Proxy for remote data/API¶
The client side applications in Cozy are constrained and cannot speak with external websites to avoid leaking personal data. Technically, it is made with the Content Security Policy. These rules are very strict and it would be a pity to not allow a client side app to load public informations from a source like Wikipedia. Our proposal is to make client side apps able to query external websites of their choices, but these requests will be made via the cozy-stack (as a proxy) and will be logged to check later that no personal data was leaked unintentionally. We can see the data loaded from the external website as a document with a doctype: it’s just not a doctype local to our cozy, but a remote one.
A client side app can request data from external websites by doing these three steps:
- Declaring a remote doctype and its requests
- Declaring permissions on these doctypes in the manifest
- Calling the
/remote
API of cozy-stack
Declaring a remote doctype¶
Doctypes are formalized in this repository:
github.com/cozy/cozy-doctypes. Each
doctype has its own directory inside the repository. For a remote doctype, it
will include a file called request
that will describe how the cozy-stack will
request the external website.
Let’s take an example:
$ tree cozy-doctypes cozy-doctypes ├── [...] ├── org.wikidata.entity │ └── request └── org.wikidata.search └── request $ cat cozy-doctypes/org.wikidata.entity/request GET https://www.wikidata.org/wiki/Special:EntityData/{{entity}}.json Accept: application/json $ cat cozy-doctypes/org.wikidata.search/request GET https://www.wikidata.org/w/api.php?action=wbsearchentities&search={{q}}&language=en&format=json
Here, we have two remote doctypes. Each one has a request defined for it.
The format for the request file is:
- the verb and the URL on the first line
- then some lines that describe the HTTP headers
- then a blank line and the body if the request is a POST
The URL can only use the default port. But, for development and testing in
local, this rule can be disabled with the --remote-allow-custom-port
flag
of the cozy-stack serve
command.
For the path, the query-string, the headers, and the body, it’s possible to have
some dynamic part by using {{
, a variable name, and }}
.
Some templating helpers are available to escape specific variables using {{
function name - space - variable name }}
. These helpers are only available for
the body part of the template.
Available helpers:
json
: for json parts ({ "key": "{{json val}}" }
)html
: for html parts (<p>{{html val}}</p>
)query
: for query parameter of a url (http://foobar.com?q={{query q}}
)path
: for path component of a url (http://foobar.com/{{path p}}
)
Values injected in the URL are automatically URI-escaped based on the part they are included in: namely as a query parameter or as a path component.
Note: by default, the User-Agent is set to a default value (“cozy-stack” and a version number). It can be overriden in the request description.
Example:
GET https://foobar.com/{{path}}?q={{query}} Content-Type: {{contentType}} { "key": "{{json value}}", "url": "http://anotherurl.com/{{path anotherPath}}?q={{query anotherQuery}}", }
Declaring permissions¶
Nothing special here. The client side app must declare that it will use these doctypes in its manifest, like for other doctypes:
{ "...": "...", "permissions": { "search": { "description": "Required for searching on wikidata", "type": "org.wikidata.search" }, "entity": { "description": "Required for getting more informations about an entity on wikidata", "type": "org.wikidata.entity" } } }
Calling the remote API¶
GET/POST /remote/:doctype
¶
The client side app must use the same verb as the defined request (GET
in our
two previous examples). It can use the query-string for GET, and a JSON body for
POST, to give values for the variables.
Example:
GET /remote/org.wikidata.search?q=Douglas+Adams HTTP/1.1 Host: alice.cozy.localhost
It is possible to send some extra informations, to make it easier to understand the request. If no variable in the request matches it, it won’t be send to the remote website.
Example:
POST /remote/org.example HTTP/1.1 Host: alice.cozy.localhost Content-Type: application/json
{ "query": "Qbhtynf Nqnzf", "comment": "query is rot13 for Douglas Adams" }
Note: currently, only the response with a content-type that is an image, JSON or XML are accepted. Other content-types are blocked the time to evaluate if they are useful and their security implication (javascript is probably not something we want to allow).
GET /remote/_all_doctypes
¶
This endpoint lists all the known remote doctypes. A permission on
io.cozy.doctypes
for GET
is needed to query this endoint.
Example:
GET /remote/_all_doctypes HTTP/1.1
HTTP/1.1 200 OK Content-Type: application/json
["cc.cozycloud.dacc", "cc.cozycloud.errors", "org.wikidata.entity", "org.wikidata.search"]
GET /remote/assets/:asset-name
¶
The client application can fetch a list of predefined assets via this route. The resources available are defined in the configuration file.
Example:
GET /remote/assets/bank HTTP/1.1 Host: alice.cozy.localhost
Logs¶
The requests are logged as the io.cozy.remote.requests
doctype, with the
doctype asked, the parameter (even those that have not been used, like comment
in the previous example), and the application that has made the request.
Secrets¶
It is possible to make the stack inject a secret in a request.
For example, if we want to make the stack inject a token
for
the request, then:
1- We need to store the secret in CouchDB, in the
secrets/io-cozy-remote-secrets
database. The document must
have the remote doctype as an id, and the secret in another field,
like this:
{ "_id": "cc.cozycloud.foobar", "token": "1c7f3ba03bd801391a91543d7eb8149c" }
2- Use this secret in the remote doctype declaration:
GET https://foobar.com/baz/ HTTP/1.1 Authorization: Bearer {{secret_token}}
You can see that we store token
in the io-cozy-remote-secret
but we use secret_token
in the remote doctype declaration, this
is a convention to follow. Like that, we quickly know what come
from secrets and what come from the caller.
If you use secret and you get a 400 Bad Request error
a variable is used in the template, but no value was given
check if you follow this convention.
For developers¶
If you are a developer and you want to use a new remote doctype, it can be
difficult to first make it available in the github.com/cozy/cozy-doctypes
repository and only then test it. So, the cozy-stack serve command has a
--doctypes
option to gives a local directory with the doctypes. You can fork
the repository, clone it, work on a new doctype inside, test it locally, and
when OK, make a pull request for it.