├── CHANGELOG.md ├── README.md ├── build.js ├── link-property-registration.txt ├── release ├── draft-dejong-remotestorage-00.txt ├── draft-dejong-remotestorage-01.txt ├── draft-dejong-remotestorage-02.txt ├── draft-dejong-remotestorage-03.txt ├── draft-dejong-remotestorage-04.txt ├── draft-dejong-remotestorage-05.txt ├── draft-dejong-remotestorage-06.txt ├── draft-dejong-remotestorage-07.txt ├── draft-dejong-remotestorage-08.txt ├── draft-dejong-remotestorage-09.txt ├── draft-dejong-remotestorage-10.txt ├── draft-dejong-remotestorage-11.txt ├── draft-dejong-remotestorage-12.txt ├── draft-dejong-remotestorage-13.txt ├── draft-dejong-remotestorage-14.txt ├── draft-dejong-remotestorage-15.txt ├── draft-dejong-remotestorage-16.txt ├── draft-dejong-remotestorage-17.txt ├── draft-dejong-remotestorage-18.txt ├── draft-dejong-remotestorage-19.txt ├── draft-dejong-remotestorage-20.txt ├── draft-dejong-remotestorage-21.txt ├── draft-dejong-remotestorage-22.txt ├── draft-dejong-remotestorage-23.txt ├── draft-dejong-remotestorage-24.txt ├── remotestorage-2010.12.html ├── remotestorage-2011.04.html ├── remotestorage-2011.10.wiki └── remotestorage-2012.04.wiki └── source.txt /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # draft-dejong-remotestorage-14.txt 2 | 3 | No changes, just extended the expiry date by 6 months. 4 | 5 | # draft-dejong-remotestorage-13.txt 6 | 7 | No changes, just extended the expiry date by 6 months. 8 | 9 | # draft-dejong-remotestorage-12.txt 10 | 11 | ## Breaking for clients and servers: 12 | * a document and a subfolder within the same folder cannot have the same 13 | name 14 | 15 | # draft-dejong-remotestorage-11.txt 16 | 17 | ## Breaking for servers: 18 | * last-modified date should now be included for document items in a folder 19 | description. 20 | 21 | ## Non-breaking: 22 | * The list of http response codes was ordered by code, increasing. 23 | * A duplicate paragraph was removed. 24 | * The use of the 412 Precondition Failed response code was clarified. 25 | 26 | # draft-dejong-remotestorage-10.txt 27 | 28 | No changes, just extended the expiry date by 6 months. 29 | 30 | # draft-dejong-remotestorage-09.txt 31 | 32 | ## Breaking for clients: 33 | * The content-type header value on a PUT request is now explicitly required 34 | to be a valid Content-Type. This is only to align with the HTTP spec, and 35 | server implementations are encouraged to still just treat it as an opaque 36 | ASCII string, rather than comparing it to some whitelist of valid values. 37 | 38 | # draft-dejong-remotestorage-08.txt 39 | 40 | No changes, just extended the expiry date by 6 months. 41 | 42 | # draft-dejong-remotestorage-07.txt 43 | 44 | ## Breaking for servers: 45 | * The app manifest is no longer guaranteed to contain a 'datastores-access' 46 | field. 47 | 48 | ## Breaking for clients: 49 | * Zero-click login when the app is opened through the `remotestorage=` URL 50 | fragment parameter (the storage-first flow) is no longer allowed, the app 51 | should now ask for user confirmation before proceeding to connect. Also, if a 52 | `remotestorage=` parameter is present, the `access_token=` and `scope=` 53 | parameters should be ignored. 54 | 55 | # draft-dejong-remotestorage-06.txt 56 | 57 | ## Breaking for servers as well as clients: 58 | * The difference between 401 and 403 http response status was clarified to match 59 | the way they are defined by the Bearer token spec. 60 | * Content-Range headers are no longer allowed on PUT requests. 61 | * The Expires: 0 header was replaced by Cache-Control: no-cache. 62 | * The WebFinger examples were updated to conform to the WebFinger spec. This 63 | includes changing `false` values to `null`, for example. 64 | 65 | ## Breaking for servers: 66 | * Apart from GET requests, HEAD requests are also allowed without Authorization 67 | request header on public documents. 68 | * Servers that support range requests should now announce this not only through 69 | WebFinger, but also through the HTTP 'Accept-Ranges' header. 70 | 71 | ## Breaking for clients: 72 | * Apart from acct:me@mydomain.com ('me@mydomain.com' in UI), http://mydomain.com/ 73 | ('mydomain.com' in UI) is now allowed as a user address for WebFinger discovery. 74 | * Access-Control-Allow-Origin: * is now also allowed on requests with preflight. 75 | This was changed in the CORS spec, and has already been implemented by all major 76 | browsers. 77 | * Item names '.' and '..' are no longer allowed. 78 | 79 | # draft-dejong-remotestorage-05.txt 80 | 81 | ## Breaking for servers as well as clients: 82 | * The link relation in the WebFinger announcement was updated from 'remotestorage' 83 | to 'http://tools.ietf.org/id/draft-dejong-remotestorage' (issue #78). 84 | * The version string in the WebFinger announcement was updated from -04 to -05. 85 | 86 | ## Breaking for clients: 87 | * Servers MAY respond with a 4xx response code if the Content-Type is not an 88 | ASCII string, is too long, is missing altogether, etc. (issues #84 89 | and #86). 90 | 91 | # draft-dejong-remotestorage-04.txt 92 | 93 | ## Breaking for servers as well as clients: 94 | * The version string in the WebFinger announcement was updated from -03 to -04 95 | * Implicit auth is now indicated with a `null` property instead of `false`. 96 | * The way to announce support for query parameter bearer tokens and range requests has changed, both for servers that do support it, and servers that don't. 97 | 98 | ## Non-breaking: 99 | * Servers may now offer any extension features they want. 100 | * Several mistakes in the text and wire examples were fixed. 101 | * Several confusing formulations in the text were improved. 102 | * Mention "group accounts", to which multiple human users have access. 103 | 104 | # draft-dejong-remotestorage-03.txt 105 | 106 | ## Breaking for servers as well as clients: 107 | * The content-type for folder listings was corrected to application/ld+json 108 | * The version string in the WebFinger announcement was updated from -02 to -03 109 | * Switch to the datastores-access syntax in open web app manifest format 110 | 111 | ## Breaking for servers: 112 | * Serving a 404 for a folder is no longer allowed; serve a folder description with zero items instead. 113 | * Servers MUST now comply with all of HTTP/1.1, including chunked uploads 114 | 115 | ## Breaking for clients: 116 | * Servers MAY now expire access tokens, in line with the OAuth spec. 117 | * Servers MAY now use Kerberos instead of OAuth. 118 | 119 | ## Non-breaking: 120 | * The option to offer a manual way to create access tokens is now mentioned 121 | * The fact that strong ETags make gzipping impossible is now mentioned 122 | * Several small changes to clarify and correct the spec text 123 | * A build script and this changelog were added to the git repo on github. 124 | 125 | # draft-dejong-remotestorage-02.txt 126 | 127 | ## Breaking for servers as well as clients: 128 | * The root scope was renamed from 'root' to '*' 129 | 130 | ## Breaking for servers: 131 | * The 'Expires: 0' caching header became obligatory on successful GET requests 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository is where we keep track of the versioning of the remoteStorage spec: 4 | 5 | https://tools.ietf.org/id/draft-dejong-remotestorage-**.txt 6 | 7 | If you would like to suggest a change in the remoteStorage spec, you can open an issue 8 | on https://github.com/remotestorage/spec/issues and discuss your proposal with the spec 9 | authors. 10 | 11 | # Versioning 12 | 13 | Each six months (max 185 days), the output is checked using idnits, submitted to the IETF 14 | as an Internet Draft, and published on the apps-discuss mailing list. 15 | 16 | Authors of remoteStorage-based apps are encouraged to use a recent version of 17 | [remotestorage.js](https://github.com/remotestorage/remotestorage.js), which aims to 18 | support each new spec version on the day it is released, at the latest. 19 | 20 | Implementers of remoteStorage servers and clients are encouraged to always offer support 21 | for the latest three versions of the spec. 22 | 23 | Storage providers should aim to expose the *previous* version of this spec, so that app 24 | authors have six months to update their apps before they become potentially incompatible. 25 | 26 | The latest three versions can be thought of as 'new', 'live', and 'old'. When for instance 27 | version 03 was published, version 02 moved from 'new' to 'live', and version 01 moved from 28 | 'live' to 'old'. 29 | 30 | So during the six months after version `k` is published, apps should add support for the 'new' 31 | version `k`, while still supporting 'live' version `k-1` and 'old' version `k-2`. 32 | Storage providers should aim to switch from 'old' version `k-2` to 'live' version `k-1` as soon 33 | as possible after version `k` is released. 34 | 35 | More info about remoteStorage in general can be found on http://remotestorage.io/ 36 | 37 | 38 | # Build 39 | 40 | To build a new version, run: 41 | 42 | ```` 43 | vim build.js # edit lines 1 and 2 44 | node build.js 45 | git status 46 | ```` 47 | 48 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const version = 24; 2 | const day = 11; 3 | const expiresDay = 1; 4 | const fileName = `./release/draft-dejong-remotestorage-${version}.txt`; 5 | 6 | if (version % 2 === 0) { 7 | month = 'December'; 8 | expiresMonth = 'June'; 9 | } else { 10 | month = 'June'; 11 | expiresMonth = 'December'; 12 | } 13 | const year = Math.floor(2013 + (version - 1) / 2); 14 | const expiresYear = Math.floor(2013 + version / 2); 15 | const date = `${day} ${month} ${year}`; 16 | const expires = `${expiresDay} ${expiresMonth} ${expiresYear}`; 17 | const monthYear = (month === 'June' ? ' ' : '') + month + ' ' + year; 18 | 19 | var fs = require('fs'), 20 | lines = fs.readFileSync('source.txt').toString().split('\n'), 21 | breaker1a = '\n\nde Jong [Page ', 22 | breaker1b = '\n\nde Jong [Page ', 23 | breaker2 = ']', 24 | breaker3 = '\n \nInternet-Draft remoteStorage '+ monthYear + '\n\n', 25 | page = 1, line = 0; 26 | 27 | let outStream = fs.createWriteStream(fileName); 28 | 29 | while (line < lines.length) { 30 | lines[line] = lines[line].split('${EXPIRES}').join(expires); 31 | lines[line] = lines[line].split('${DATE}').join(date); 32 | lines[line] = lines[line].split('${YEAR}').join(year); 33 | lines[line] = lines[line].split('${VERSION}').join(version); 34 | outStream.write(lines[line] + '\n'); 35 | if (lines[line].length > 72) { 36 | outStream.write('123456789012345678901234567890123456789012345678901234567890123456789012*****\n'); 37 | break; 38 | } 39 | line++; 40 | if (line > 7 && line%43 === 7) { 41 | outStream.write((page < 10 ? breaker1a : breaker1b) + page + breaker2 + (line < lines.length ? breaker3 : '') + '\n'); 42 | page++; 43 | } 44 | } 45 | while (line%43 !== 7) { 46 | outStream.write('\n'); 47 | line++; 48 | } 49 | outStream.write((page < 10 ? breaker1a : breaker1b) + page + breaker2 + (line < lines.length ? breaker3 : '') + '\n'); 50 | outStream.close(); 51 | -------------------------------------------------------------------------------- /link-property-registration.txt: -------------------------------------------------------------------------------- 1 | o Property Identifier: http://remotestorage.io/spec/version 2 | 3 | o Link Type: N/A 4 | 5 | o Description: For a link that points to the storage root of a user's 6 | account on a remoteStorage server, the version of the remoteStorage 7 | spec which the server implements. 8 | 9 | o Reference: https://github.com/remotestorage/spec/blob/648c5868faf260092ea2f1dfbd6e9ce2ebf553bd/draft-dejong-remotestorage-head.txt#L378 10 | 11 | o Notes: Example values are "draft-dejong-remotestorage-02", 12 | "draft-dejong-remotestorage-03", etcetera, refering to the Internet 13 | Draft versions describing the server's behavior. 14 | -------------------------------------------------------------------------------- /release/draft-dejong-remotestorage-00.txt: -------------------------------------------------------------------------------- 1 | INTERNET DRAFT Michiel B. de Jong 2 | Document: draft-dejong-remotestorage-00 (independent) 3 | F. Kooman 4 | Intended Status: Proposed Standard SURFnet 5 | Expires: 8 June 2013 5 December 2012 6 | 7 | 8 | remotestorage 9 | 10 | Abstract 11 | 12 | This draft describes a protocol by which client-side applications, 13 | running inside a web browser, can communicate with a data storage 14 | server that is hosted on a different domain name. This way, the 15 | provider of a web application need not also play the role of data 16 | storage provider. The protocol supports storing, retrieving, and 17 | removing individual documents, as well as listing the contents of an 18 | individual directory, and access control is based on bearer tokens. 19 | 20 | Status of this Memo 21 | 22 | This Internet-Draft is submitted in full conformance with the 23 | provisions of BCP 78 and BCP 79. 24 | 25 | Internet-Drafts are working documents of the Internet Engineering 26 | Task Force (IETF). Note that other groups may also distribute 27 | working documents as Internet-Drafts. The list of current Internet- 28 | Drafts is at http://datatracker.ietf.org/drafts/current/. 29 | 30 | Internet-Drafts are draft documents valid for a maximum of six months 31 | and may be updated, replaced, or obsoleted by other documents at any 32 | time. It is inappropriate to use Internet-Drafts as reference 33 | material or to cite them other than as "work in progress." 34 | 35 | This Internet-Draft will expire on 9 June 2013. 36 | 37 | Copyright Notice 38 | 39 | Copyright (c) 2012 IETF Trust and the persons identified as the 40 | document authors. All rights reserved. 41 | 42 | This document is subject to BCP 78 and the IETF Trust's Legal 43 | Provisions Relating to IETF Documents 44 | (http://trustee.ietf.org/license-info) in effect on the date of 45 | publication of this document. Please review these documents 46 | carefully, as they describe your rights and restrictions with respect 47 | to this document. Code Components extracted from this document must 48 | include Simplified BSD License text as described in Section 4.e of 49 | the Trust Legal Provisions and are provided without warranty as 50 | described in the Simplified BSD License. 51 | 52 | 53 | de Jong [Page 1] 54 | 55 | Internet-Draft remotestorage December 2012 56 | 57 | 58 | Table of Contents 59 | 60 | 1. Introduction...................................................2 61 | 2. Terminology....................................................2 62 | 3. Storage model..................................................3 63 | 4. Requests.......................................................3 64 | 5. Response codes.................................................4 65 | 6. Versioning.....................................................5 66 | 7. CORS headers...................................................5 67 | 8. Session description............................................5 68 | 9. Bearer tokens and access control...............................6 69 | 10. Application-first bearer token issuance........................6 70 | 11. Storage-first bearer token issuance............................7 71 | 12. Security Considerations........................................8 72 | 13. IANA Considerations............................................9 73 | 14. Acknowledgments................................................9 74 | 15. References.....................................................9 75 | 15.1. Normative References......................................9 76 | 15.2. Informative References....................................9 77 | 16. Authors' addresses............................................10 78 | 79 | 80 | 1. Introduction 81 | 82 | Many services for data storage are available over the internet. This 83 | specification describes a vendor-independent interface for such 84 | services. It is based on https, CORS and bearer tokens. The 85 | metaphor for addressing data on the storage is that of folders 86 | containing documents and subfolders. The actions the interface 87 | exposes are: 88 | 89 | * GET a folder: retrieve the names and current versions of the 90 | documents and subfolders currently contained by the folder 91 | 92 | * GET a document: retrieve its content type, current version, 93 | and contents 94 | 95 | * PUT a document: store a new version, its content type, and 96 | contents, conditional on the current version 97 | 98 | * DELETE a document: remove it from the storage, conditional on 99 | the current version 100 | 101 | The exact details of these four actions are described in this 102 | specification. 103 | 104 | 2. Terminology 105 | 106 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 107 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 108 | 109 | 110 | de Jong [Page 2] 111 | 112 | Internet-Draft remotestorage December 2012 113 | 114 | 115 | document are to be interpreted as described in RFC 2119 [WORDS]. 116 | 117 | "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a 118 | general requirement are known to exist or appear to exist, and it is 119 | infeasible or impractical to enumerate all of them. However, they 120 | should not be interpreted as permitting implementors to fail to 121 | implement the general requirement when such failure would result in 122 | interoperability failure. 123 | 124 | 3. Storage model 125 | 126 | The server stores data in nodes that form a tree structure. 127 | Internal nodes are called 'folders' and leaf nodes are called 128 | 'documents'. For a folder, the server stores references to nodes 129 | contained in the folder, and it should be able to produce a list of 130 | them, with for each contained item: 131 | 132 | * item name 133 | * item type (folder or document) 134 | * current version 135 | 136 | For a document, the server stores, and should be able to produce: 137 | 138 | * content type 139 | * content 140 | * current version 141 | 142 | 4. Requests 143 | 144 | Client-to-server requests SHOULD be made over https [HTTPS]. The 145 | root folder of the storage tree is represented by the URL 146 | '/'. Subsequently, if is the URL of a 147 | folder, then the URL of an item contained in it is 148 | for a document, or 149 | '/' for a folder. Item names MAY 150 | contain a-z, A-Z, 0-9, %, -, _. 151 | 152 | A successful GET request to a folder SHOULD be responded to with a 153 | JSON document (content type 'application/json'), representing a map 154 | in which contained documents appear as entries to 155 | , and contained folders appear as entries 156 | '/' to , for instance: 157 | 158 | { 159 | "abc": 1234567890123, 160 | "def/": 1234567890456 161 | } 162 | 163 | Empty folders are treated as non-existing, and therefore GET 164 | requests to them SHOULD be responded to with a 404 response, and an 165 | 166 | 167 | de Jong [Page 3] 168 | 169 | Internet-Draft remotestorage December 2012 170 | 171 | 172 | empty folder MUST NOT be listed as an item in its parent folder. 173 | Also, folders SHOULD be created silently, as necessary to contain 174 | newly added items. This way, PUT and DELETE requests only need to be 175 | made to documents, and folder management becomes an implicit result. 176 | 177 | A successful GET request to a document SHOULD be responded to with 178 | the full document contents in the body, the document's content type 179 | in a 'Content-Type' header, and the document's current version in an 180 | 'ETag' header. 181 | 182 | 183 | A successful PUT request to a document MUST result in: 184 | 185 | * the request body being stored as the document's new content, 186 | * parent and further ancestor folders being silently created as 187 | necessary, with the document (name and version) being added to 188 | its parent folder, and each folder added to its subsequent 189 | parent, 190 | * the value of its Content-Type header being stored as the 191 | document's new content type, 192 | * the current server time, in the form of milliseconds since 193 | 0:00 UCT, 1 January, 1970 being stored as the new version of 194 | the document itself, as well as of its parent folder and 195 | further ancestor folders. 196 | 197 | The response MUST contain an ETag header, with the document's new 198 | version (milliseconds since the beginning of 1970) as its value. 199 | 200 | A successful DELETE request to a document MUST result in the 201 | deletion of that document from the storage, and from its parent 202 | folder. If the parent folder is left empty by this, then it MUST 203 | also be removed, and so on for ancestor folders. 204 | 205 | A successful OPTIONS request SHOULD be responded to as described in 206 | the CORS section below. 207 | 208 | 5. Response codes 209 | 210 | The following responses SHOULD be given in the indicated cases, in 211 | order of preference, and SHOULD be recognized by the client: 212 | 213 | * 500 if an internal server error occurs, 214 | * 420 if the client makes too frequent requests or is suspected 215 | of malicious activity, 216 | * 400 for all malformed requests (e.g. foreign characters in the 217 | path or unrecognized http verb, etcetera), as well as for 218 | all PUT and DELETE requests to folders, 219 | * 401 for all requests that don't have a bearer token with 220 | sufficient permissions, 221 | * 404 for all DELETE and GET requests to nodes that do not exist 222 | 223 | 224 | de Jong [Page 4] 225 | 226 | Internet-Draft remotestorage December 2012 227 | 228 | 229 | on the storage, 230 | * 304 for a conditional GET request whose condition fails 231 | (see "Versioning" below), 232 | * 409 for a conditional PUT or DELETE request whose condition 233 | fails (see "Versioning" below), 234 | * 200 for all successful requests, including PUT and DELETE, 235 | 236 | Clients SHOULD also handle the case where a response takes too long 237 | to arrive, or where no response is received at all. 238 | 239 | 6. Versioning 240 | 241 | The current version of a document is the 13-digit decimal number 242 | representing the number of milliseconds between 00:00 UCT, 1 January 243 | 1970, and the last time its content or content type were set or 244 | changed successfully. The current version of a folder is the highest 245 | version of any of the items it contains. 246 | 247 | All successful requests MUST return an 'ETag' header with, in the 248 | case of GET, the current version, in the case of PUT, the new 249 | version, and in case of DELETE, the version that was deleted. PUT 250 | and DELETE requests MAY have an 'If-Unmodified-Since' request 251 | header, and MUST fail with a 409 response code if that doesn't match 252 | the document's current version. GET requests MAY have an 253 | 'If-Modified-Since' header, and SHOULD be responded to with a 304 if 254 | that matches the document or folder's current version. 255 | 256 | 7. CORS headers 257 | 258 | All responses MUST carry CORS headers [CORS]. The server MUST also 259 | reply to OPTIONS requests as per CORS. For GET requests, a wildcard 260 | origin MAY be returned, but for PUT and DELETE requests, the 261 | response MUST echo back the Origin header sent by the client. 262 | 263 | 8. Session description 264 | 265 | The information that a client needs to receive in order to be able 266 | to connect to a server SHOULD reach the client as described in the 267 | 'bearer token issuance' sections below. It consists of: 268 | 269 | * , consisting of 'https://' followed by a server 270 | host, and optionally a server port and a path prefix as per 271 | [IRI]. Examples: 272 | * 'https://example.com' (host only) 273 | * 'https://example.com:8080' (host and port) 274 | * 'https://example.com/some?path/to/storage' (host, port and 275 | path prefix; note there is no trailing slash) 276 | * as per [OAUTH]. The token SHOULD be hard to 277 | guess and SHOULD NOT be reused from one client to another. It 278 | can however be reused in subsequent interactions with the same 279 | 280 | 281 | de Jong [Page 5] 282 | 283 | Internet-Draft remotestorage December 2012 284 | 285 | 286 | client, as long as that client is still trusted. Example: 287 | * 'ofb24f1ac3973e70j6vts19qr9v2eei' 288 | * , always 'draft-dejong-remotestorage-00' for this 289 | version of the specification. 290 | 291 | The client can make its requests using https with CORS and bearer 292 | tokens, to the URL that is the concatenation of with 293 | '/' plus one or more '/' strings indicating a path in the 294 | folder tree, followed by zero or one strings, indicating 295 | a document. For example, if is 296 | "https://storage.example.com/bob", then to retrieve the folder 297 | contents of the /public/documents/ folder, or to retrieve a 298 | 'draft.txt' document from that folder, the client would make 299 | requests to, respectively: 300 | 301 | * https://storage.example.com/bob/public/documents/ 302 | * https://storage.example.com/bob/public/documents/draft.txt 303 | 304 | 9. Bearer tokens and access control 305 | 306 | A bearer token represents one or more access scopes. These access 307 | scopes are represented as strings of the form , 308 | where the string SHOULD be lower-case alphanumerical, other 309 | than the reserved word 'public', and can be ':r' or ':rw'. 310 | The access the bearer token gives is the sum of its access scopes, 311 | with each access scope representing the following permissions: 312 | 313 | 'root:rw') any request, 314 | 315 | 'root:r') any GET request, 316 | 317 | ':rw') any requests to paths that start with 318 | '/' '/' or '/public/' '/', 319 | 320 | ':r') any GET requests to paths that start with 321 | '/' '/' or '/public/' '/', 322 | 323 | As a special exceptions, GET requests to a document (but not a 324 | folder) whose path starts with '/public/' are always allowed. They, 325 | as well as OPTIONS requests, can be made without a bearer token. All 326 | other requests should present a bearer token with sufficient access 327 | scope, using a header of the following form: 328 | 329 | Authorization: Bearer 330 | 331 | 10. Application-first bearer token issuance 332 | 333 | To make a remotestorage server available as 'the remotestorage of 334 | at ', exactly one link of the following format SHOULD 335 | be added to the webfinger record [WEBFINGER] of at : 336 | 337 | 338 | de Jong [Page 6] 339 | 340 | Internet-Draft remotestorage December 2012 341 | 342 | 343 | { 344 | href: , 345 | rel: "remotestorage", 346 | type: , 347 | properties: { 348 | 'auth-method': "http://tools.ietf.org/html/rfc6749#section-4.2", 349 | 'auth-endpoint': 350 | } 351 | } 352 | 353 | Here and are as per "Session 354 | description" above, and SHOULD be a URL where an 355 | OAuth2 implicit-grant flow dialog [OAUTH] is be presented, so the 356 | user can supply her credentials (how, is out of scope), and allow or 357 | reject a request by the connecting application to obtain a bearer 358 | token for a certain list of access scopes. 359 | 360 | The server SHOULD NOT expire bearer tokens unless they are revoked, 361 | and MAY require the user to register applications as OAuth clients 362 | before first use; if no client registration is required, then the 363 | server MAY ignore the client_id parameter in favour of relying on 364 | the redirect_uri parameter for client identification. 365 | 366 | 11. Storage-first bearer token issuance 367 | 368 | The provider MAY also present a dashboard to the user, where she 369 | has some way to add open web app manifests [MANIFEST]. Adding a 370 | manifest to the dashboard is considered equivalent to clicking 371 | 'accept' in the dialog of the application-first flow. Removing one 372 | is considered equivalent to revoking its access token. 373 | 374 | As an equivalent to OAuth's 'scope' parameter, a 'remotestorage' 375 | field SHOULD be present in the root of such an application manifest 376 | document, as a JSON array of strings, each string being one access 377 | scope of the form . 378 | 379 | When the user gestures she wants to use a certain application whose 380 | manifest is present on the dashboard, the dashboard SHOULD redirect 381 | to the application or open it in a new window. To mimic coming back 382 | from the OAuth dialog, it MAY add 'access_token' and 'scope' 383 | parameters to the URL fragment. 384 | 385 | Regardless of whether 'access_token' and 'scope' are specified, it 386 | SHOULD add a 'remotestorage' parameter to the URL fragment, with a 387 | value of the form '@' . When the application detects 388 | this parameter, it SHOULD resolve the webfinger record for at 389 | and extract the and information. 390 | 391 | If no access_token was given, then the application SHOULD also 392 | extract the information from webfinger, and continue 393 | 394 | 395 | de Jong [Page 7] 396 | 397 | Internet-Draft remotestorage December 2012 398 | 399 | 400 | as per application-first bearer token issuance. 401 | 402 | Note that whereas a remotestorage server SHOULD offer support of the 403 | application-first flow with webfinger and OAuth, it MAY choose not 404 | to support the storage-first flow, provided that users will easily 405 | remember their '@' webfinger address at that provider. 406 | Applications SHOULD, however, support both flows, which means 407 | checking the URL for a 'remotestorage' parameter, but giving the 408 | user a way to specify her webfinger address if there is none. 409 | 410 | If a server provides an application manifest dashboard, then it 411 | SHOULD merge the list of applications there with the list of 412 | issued access tokens as specified by OAuth into one list. Also, 413 | the interface for revoking an access token as specified by OAuth 414 | SHOULD coincide with removing an application from the dashboard. 415 | 416 | 12. Security Considerations 417 | 418 | To prevent man-in-the-middle attacks, the use of https instead of 419 | http is important for both the interface itself and all end-points 420 | involved in webfinger, OAuth, and (if present) the storage-first 421 | application launch dashboard. 422 | 423 | A malicious party could link to an application, but specifying a 424 | remotestorage user address that it controls, thus tricking the user 425 | into using a trusted application to send sensitive data to the wrong 426 | remotestorage server. To mitigate this, applications SHOULD clearly 427 | display to which remotestorage server they are sending the user's 428 | data. 429 | 430 | Applications could request scopes that the user did not intend to 431 | give access to. The user SHOULD always be prompted to carefully 432 | review which scopes an application is requesting. 433 | 434 | An application may upload malicious html pages and then trick the 435 | user into visiting them, or upload malicious client-side scripts, 436 | that take advantage of being hosted on the user's domain name. The 437 | origin on which the remotestorage server has its interface SHOULD 438 | therefore NOT be used for anything else, and the user SHOULD be 439 | warned not to visit any web pages on that origin. In particular, the 440 | OAuth dialog and launch dashboard or token revokation interface 441 | SHOULD be on a different origin than the remotestorage interface. 442 | 443 | Where the use of bearer tokens is impractical, a user may choose to 444 | store documents on hard-to-guess URLs whose path after 445 | starts with '/public/', while sharing this URL only 446 | with the intended audience. That way, only parties who know the 447 | document's hard-to-guess URL, can access it. The server SHOULD 448 | therefore make an effort to detect and stop brute-force attacks that 449 | attempt to guess the location of such documents. 450 | 451 | 452 | de Jong [Page 8] 453 | 454 | Internet-Draft remotestorage December 2012 455 | 456 | 457 | The server SHOULD also detect and stop denial-of-service attacks 458 | that aim to overwhelm its interface with too much traffic. 459 | 460 | 13. IANA Considerations 461 | 462 | This document registers the 'remotestorage' link relation. 463 | 464 | 14. Acknowledgements 465 | 466 | The authors would like to thank everybody who contributed to the 467 | development of this protocol, including Kenny Bentley, Javier Diaz, 468 | Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter 469 | Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph 470 | Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin 471 | Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco 472 | Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick 473 | Jennings, and Markus Sabadello, among many others. 474 | 475 | 15. References 476 | 477 | 15.1. Normative References 478 | 479 | [WORDS] 480 | Bradner, S., "Key words for use in RFCs to Indicate Requirement 481 | Levels", BCP 14, RFC 2119, March 1997. 482 | 483 | [IRI] 484 | Duerst, M., "Internationalized Resource Identifiers (IRIs)", 485 | RFC 3987, January 2005. 486 | 487 | [WEBFINGER] 488 | Jones, Paul E., Salguerio, Gonzalo, and Smarr, Joseph, 489 | "WebFinger", draft-ietf-appsawg-webfinger-07, Work in Progress 490 | 491 | [OAUTH] 492 | "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth 493 | 2.0 Authorization Framework", RFC6749, October 2012. 494 | 495 | 15.2. Informative References 496 | 497 | [HTTPS] 498 | Rescorla, E., "HTTP Over TLS", RFC2818, May 2000. 499 | 500 | [CORS] 501 | van Kesteren, Anne (ed), "Cross-Origin Resource Sharing -- W3C 502 | Working Draft 3 April 2012", 503 | http://www.w3.org/TR/2012/WD-cors-20120403/CORS, April 2012. 504 | 505 | [MANIFEST] 506 | Mozilla Developer Network (ed), "App manifest -- Revision 507 | 508 | 509 | de Jong [Page 9] 510 | 511 | Internet-Draft remotestorage December 2012 512 | 513 | 514 | 330541", https://developer.mozilla.org/en- 515 | US/docs/Apps/Manifest$revision/330541, November 2012. 516 | 517 | 16. Authors' addresses 518 | 519 | Michiel B. de Jong 520 | (independent) 521 | 522 | Email: michiel@michielbdejong.com 523 | 524 | 525 | F. Kooman 526 | SURFnet bv 527 | Postbus 19035 528 | 3501 DA Utrecht 529 | The Netherlands 530 | 531 | Email: Francois.Kooman@surfnet.nl 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | de Jong [Page 10] 567 | -------------------------------------------------------------------------------- /release/draft-dejong-remotestorage-01.txt: -------------------------------------------------------------------------------- 1 | INTERNET DRAFT Michiel B. de Jong 2 | Document: draft-dejong-remotestorage-01 (independent) 3 | F. Kooman 4 | Intended Status: Proposed Standard SURFnet 5 | Expires: 10 December 2013 8 June 2013 6 | 7 | 8 | remoteStorage 9 | 10 | Abstract 11 | 12 | This draft describes a protocol by which client-side applications, 13 | running inside a web browser, can communicate with a data storage 14 | server that is hosted on a different domain name. This way, the 15 | provider of a web application need not also play the role of data 16 | storage provider. The protocol supports storing, retrieving, and 17 | removing individual documents, as well as listing the contents of an 18 | individual directory, and access control is based on bearer tokens. 19 | 20 | Status of this Memo 21 | 22 | This Internet-Draft is submitted in full conformance with the 23 | provisions of BCP 78 and BCP 79. 24 | 25 | Internet-Drafts are working documents of the Internet Engineering 26 | Task Force (IETF). Note that other groups may also distribute 27 | working documents as Internet-Drafts. The list of current Internet- 28 | Drafts is at http://datatracker.ietf.org/drafts/current/. 29 | 30 | Internet-Drafts are draft documents valid for a maximum of six months 31 | and may be updated, replaced, or obsoleted by other documents at any 32 | time. It is inappropriate to use Internet-Drafts as reference 33 | material or to cite them other than as "work in progress." 34 | 35 | This Internet-Draft will expire on 10 December 2013. 36 | 37 | Copyright Notice 38 | 39 | Copyright (c) 2013 IETF Trust and the persons identified as the 40 | document authors. All rights reserved. 41 | 42 | This document is subject to BCP 78 and the IETF Trust's Legal 43 | Provisions Relating to IETF Documents 44 | (http://trustee.ietf.org/license-info) in effect on the date of 45 | publication of this document. Please review these documents 46 | carefully, as they describe your rights and restrictions with respect 47 | to this document. Code Components extracted from this document must 48 | include Simplified BSD License text as described in Section 4.e of 49 | the Trust Legal Provisions and are provided without warranty as 50 | described in the Simplified BSD License. 51 | 52 | 53 | de Jong [Page 1] 54 | 55 | Internet-Draft remoteStorage June 2013 56 | 57 | 58 | Table of Contents 59 | 60 | 1. Introduction...................................................2 61 | 2. Terminology....................................................2 62 | 3. Storage model..................................................3 63 | 4. Requests.......................................................3 64 | 5. Response codes.................................................4 65 | 6. Versioning.....................................................5 66 | 7. CORS headers...................................................5 67 | 8. Session description............................................5 68 | 9. Bearer tokens and access control...............................6 69 | 10. Application-first bearer token issuance........................6 70 | 11. Storage-first bearer token issuance............................7 71 | 12. Security Considerations........................................8 72 | 13. IANA Considerations............................................9 73 | 14. Acknowledgments................................................9 74 | 15. References.....................................................9 75 | 15.1. Normative References......................................9 76 | 15.2. Informative References....................................9 77 | 16. Authors' addresses............................................10 78 | 79 | 80 | 1. Introduction 81 | 82 | Many services for data storage are available over the internet. This 83 | specification describes a vendor-independent interface for such 84 | services. It is based on https, CORS and bearer tokens. The 85 | metaphor for addressing data on the storage is that of folders 86 | containing documents and subfolders. The actions the interface 87 | exposes are: 88 | 89 | * GET a folder: retrieve the names and current versions of the 90 | documents and subfolders currently contained by the folder 91 | 92 | * GET a document: retrieve its content type, current version, 93 | and contents 94 | 95 | * PUT a document: store a new version, its content type, and 96 | contents, conditional on the current version 97 | 98 | * DELETE a document: remove it from the storage, conditional on 99 | the current version 100 | 101 | The exact details of these four actions are described in this 102 | specification. 103 | 104 | 105 | 106 | de Jong [Page 2] 107 | 108 | Internet-Draft remoteStorage June 2013 109 | 110 | 111 | 2. Terminology 112 | 113 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 114 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 115 | document are to be interpreted as described in RFC 2119 [WORDS]. 116 | 117 | "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a 118 | general requirement are known to exist or appear to exist, and it is 119 | infeasible or impractical to enumerate all of them. However, they 120 | should not be interpreted as permitting implementors to fail to 121 | implement the general requirement when such failure would result in 122 | interoperability failure. 123 | 124 | 3. Storage model 125 | 126 | The server stores data in nodes that form a tree structure. 127 | Internal nodes are called 'folders' and leaf nodes are called 128 | 'documents'. For a folder, the server stores references to nodes 129 | contained in the folder, and it should be able to produce a list of 130 | them, with for each contained item: 131 | 132 | * item name 133 | * item type (folder or document) 134 | * current version 135 | 136 | For a document, the server stores, and should be able to produce: 137 | 138 | * content type 139 | * content 140 | * current version 141 | 142 | 4. Requests 143 | 144 | Client-to-server requests SHOULD be made over https [HTTPS]. The 145 | root folder of the storage tree is represented by the URL 146 | '/'. Subsequently, if is the URL of a 147 | folder, then the URL of an item contained in it is 148 | for a document, or 149 | '/' for a folder. Item names MAY 150 | contain a-z, A-Z, 0-9, %, ., -, _, and MUST NOT have zero length. 151 | 152 | A successful GET request to a folder SHOULD be responded to with a 153 | JSON document (content type 'application/json'), representing a map 154 | in which contained documents appear as entries to 155 | , and contained folders appear as entries 156 | '/' to , for instance: 157 | 158 | 159 | de Jong [Page 3] 160 | 161 | Internet-Draft remoteStorage June 2013 162 | 163 | 164 | { 165 | "abc": "DEADBEEFDEADBEEFDEADBEEF", 166 | "def/": "1337ABCD1337ABCD1337ABCD" 167 | } 168 | 169 | Empty folders are treated as non-existing, and therefore GET 170 | requests to them SHOULD be responded to with a 404 response, and an 171 | empty folder MUST NOT be listed as an item in its parent folder. 172 | Also, folders SHOULD be created silently, as necessary to contain 173 | newly added items. This way, PUT and DELETE requests only need to be 174 | made to documents, and folder management becomes an implicit result. 175 | 176 | A successful GET request to a document SHOULD be responded to with 177 | the full document contents in the body, the document's content type 178 | in a 'Content-Type' header, and the document's current version in an 179 | 'ETag' header. 180 | 181 | 182 | A successful PUT request to a document MUST result in: 183 | 184 | * the request body being stored as the document's new content, 185 | * parent and further ancestor folders being silently created as 186 | necessary, with the document (name and version) being added to 187 | its parent folder, and each folder added to its subsequent 188 | parent, 189 | * the value of its Content-Type header being stored as the 190 | document's new content type, 191 | * its version being updated, as well as that of its parent folder 192 | and further ancestor folders. 193 | 194 | The response MUST contain an ETag header, with the document's new 195 | version (for instance a hash of its contents) as its value. 196 | 197 | A successful DELETE request to a document MUST result in: 198 | 199 | * the deletion of that document from the storage, and from its 200 | parent folder, 201 | * silent deletion of the parent folder if it is left empty by 202 | this, and so on for further ancestor folders, 203 | * the version of its parent folder being updated, as well as that 204 | of further ancestor folders. 205 | 206 | The response MUST contain an ETag header, with the document's 207 | version that was deleted as its value. 208 | 209 | A successful OPTIONS request SHOULD be responded to as described in 210 | the CORS section below. 211 | 212 | 5. Response codes 213 | 214 | The following responses SHOULD be given in the indicated cases, in 215 | order of preference, and SHOULD be recognized by the client: 216 | 217 | 218 | 219 | de Jong [Page 4] 220 | 221 | Internet-Draft remoteStorage June 2013 222 | 223 | 224 | * 500 if an internal server error occurs, 225 | * 420 if the client makes too frequent requests or is suspected 226 | of malicious activity, 227 | * 400 for all malformed requests (e.g. foreign characters in the 228 | path or unrecognized http verb, etcetera), as well as for 229 | all PUT and DELETE requests to folders, 230 | * 401 for all requests that don't have a bearer token with 231 | sufficient permissions, 232 | * 404 for all DELETE and GET requests to nodes that do not exist 233 | on the storage, 234 | * 412 for a conditional request whose pre-condition 235 | fails (see "Versioning" below), 236 | * 507 in case the user's account is over its storage quota, 237 | * 200 for all successful requests, including PUT and DELETE. 238 | 239 | Clients SHOULD also handle the case where a response takes too long 240 | to arrive, or where no response is received at all. 241 | 242 | 6. Versioning 243 | 244 | All successful requests MUST return an 'ETag' header [HTTP] with, 245 | in the case of GET, the current version, in the case of PUT, the 246 | new version, and in case of DELETE, the version that was deleted. 247 | PUT and DELETE requests MAY have an 'If-Match' request header 248 | [HTTP], and MUST fail with a 412 response code if that doesn't match 249 | the document's current version. GET requests MAY have an 250 | 'If-None-Match' header [HTTP], and SHOULD be responded to with a 412 251 | response if that includes the document or folder's current version. 252 | 253 | A PUT request MAY have an 'If-None-Match:*' header [HTTP], in which 254 | case it MUST fail with a 412 response code if the document already 255 | exists. 256 | 257 | A provider MAY offer its users a way to roll back to a previous 258 | version of the storage contents, but this specification does not 259 | define any such rollback functionality. 260 | 261 | 7. CORS headers 262 | 263 | All responses MUST carry CORS headers [CORS]. The server MUST also 264 | reply to OPTIONS requests as per CORS. For GET requests, a wildcard 265 | origin MAY be returned, but for PUT and DELETE requests, the 266 | response MUST echo back the Origin header sent by the client. 267 | 268 | 269 | 270 | 271 | 272 | de Jong [Page 5] 273 | 274 | Internet-Draft remoteStorage June 2013 275 | 276 | 277 | 8. Session description 278 | 279 | The information that a client needs to receive in order to be able 280 | to connect to a server SHOULD reach the client as described in the 281 | 'bearer token issuance' sections below. It consists of: 282 | 283 | * , consisting of 'https://' followed by a server 284 | host, and optionally a server port and a path prefix as per 285 | [IRI]. Examples: 286 | * 'https://example.com' (host only) 287 | * 'https://example.com:8080' (host and port) 288 | * 'https://example.com/path/to/storage' (host, port and 289 | path prefix; note there is no trailing slash) 290 | * as per [OAUTH]. The token SHOULD be hard to 291 | guess and SHOULD NOT be reused from one client to another. It 292 | can however be reused in subsequent interactions with the same 293 | client, as long as that client is still trusted. Example: 294 | * 'ofb24f1ac3973e70j6vts19qr9v2eei' 295 | * , always 'draft-dejong-remotestorage-01' for this 296 | version of the specification. 297 | 298 | The client can make its requests using https with CORS and bearer 299 | tokens, to the URL that is the concatenation of with 300 | '/' plus one or more '/' strings indicating a path in the 301 | folder tree, followed by zero or one strings, indicating 302 | a document. For example, if is 303 | "https://storage.example.com/bob", then to retrieve the folder 304 | contents of the /public/documents/ folder, or to retrieve a 305 | 'draft.txt' document from that folder, the client would make 306 | requests to, respectively: 307 | 308 | * https://storage.example.com/bob/public/documents/ 309 | * https://storage.example.com/bob/public/documents/draft.txt 310 | 311 | 9. Bearer tokens and access control 312 | 313 | A bearer token represents one or more access scopes. These access 314 | scopes are represented as strings of the form , 315 | where the string SHOULD be lower-case alphanumerical, other 316 | than the reserved word 'public', and can be ':r' or ':rw'. 317 | The access the bearer token gives is the sum of its access scopes, 318 | with each access scope representing the following permissions: 319 | 320 | 'root:rw') any request, 321 | 322 | 'root:r') any GET request, 323 | 324 | 325 | de Jong [Page 6] 326 | 327 | Internet-Draft remoteStorage June 2013 328 | 329 | 330 | ':rw') any requests to paths that start with 331 | '/' '/' or '/public/' '/', 332 | 333 | ':r') any GET requests to paths that start with 334 | '/' '/' or '/public/' '/', 335 | 336 | As a special exceptions, GET requests to a document (but not a 337 | folder) whose path starts with '/public/' are always allowed. They, 338 | as well as OPTIONS requests, can be made without a bearer token. All 339 | other requests should present a bearer token with sufficient access 340 | scope, using a header of the following form: 341 | 342 | Authorization: Bearer 343 | 344 | 345 | 10. Application-first bearer token issuance 346 | 347 | To make a remoteStorage server available as 'the remoteStorage of 348 | at ', exactly one link of the following format SHOULD 349 | be added to the webfinger record [WEBFINGER] of at : 350 | 351 | { 352 | href: , 353 | rel: "remotestorage", 354 | type: , 355 | properties: { 356 | "http://tools.ietf.org/html/rfc6749#section-4.2": 357 | } 358 | } 359 | 360 | Here and are as per "Session 361 | description" above, and SHOULD be a URL where an 362 | OAuth2 implicit-grant flow dialog [OAUTH] is be presented, so the 363 | user can supply her credentials (how, is out of scope), and allow or 364 | reject a request by the connecting application to obtain a bearer 365 | token for a certain list of access scopes. 366 | 367 | The server SHOULD NOT expire bearer tokens unless they are revoked, 368 | and MAY require the user to register applications as OAuth clients 369 | before first use; if no client registration is required, then the 370 | server MAY ignore the client_id parameter in favour of relying on 371 | the redirect_uri parameter for client identification. 372 | 373 | 11. Storage-first bearer token issuance 374 | 375 | The provider MAY also present a dashboard to the user, where she 376 | 377 | 378 | de Jong [Page 7] 379 | 380 | Internet-Draft remoteStorage June 2013 381 | 382 | 383 | has some way to add open web app manifests [MANIFEST]. Adding a 384 | manifest to the dashboard is considered equivalent to clicking 385 | 'accept' in the dialog of the application-first flow. Removing one 386 | is considered equivalent to revoking its access token. 387 | 388 | As an equivalent to OAuth's 'scope' parameter, a 'remotestorage' 389 | field SHOULD be present in the root of such an application manifest 390 | document, as a JSON array of strings, each string being one access 391 | scope of the form . 392 | 393 | When the user gestures she wants to use a certain application whose 394 | manifest is present on the dashboard, the dashboard SHOULD redirect 395 | to the application or open it in a new window. To mimic coming back 396 | from the OAuth dialog, it MAY add 'access_token' and 'scope' 397 | parameters to the URL fragment. 398 | 399 | Regardless of whether 'access_token' and 'scope' are specified, it 400 | SHOULD add a 'remotestorage' parameter to the URL fragment, with a 401 | value of the form '@' . When the application detects 402 | this parameter, it SHOULD resolve the webfinger record for at 403 | and extract the and information. 404 | 405 | If no access_token was given, then the application SHOULD also 406 | extract the information from webfinger, and continue 407 | as per application-first bearer token issuance. 408 | 409 | Note that whereas a remoteStorage server SHOULD offer support of the 410 | application-first flow with webfinger and OAuth, it MAY choose not 411 | to support the storage-first flow, provided that users will easily 412 | remember their '@' webfinger address at that provider. 413 | Applications SHOULD, however, support both flows, which means 414 | checking the URL for a 'remoteStorage' parameter, but giving the 415 | user a way to specify her webfinger address if there is none. 416 | 417 | If a server provides an application manifest dashboard, then it 418 | SHOULD merge the list of applications there with the list of 419 | issued access tokens as specified by OAuth into one list. Also, 420 | the interface for revoking an access token as specified by OAuth 421 | SHOULD coincide with removing an application from the dashboard. 422 | 423 | 12. Security Considerations 424 | 425 | To prevent man-in-the-middle attacks, the use of https instead of 426 | http is important for both the interface itself and all end-points 427 | involved in webfinger, OAuth, and (if present) the storage-first 428 | application launch dashboard. 429 | 430 | 431 | de Jong [Page 8] 432 | 433 | Internet-Draft remoteStorage June 2013 434 | 435 | 436 | A malicious party could link to an application, but specifying a 437 | remoteStorage user address that it controls, thus tricking the user 438 | into using a trusted application to send sensitive data to the wrong 439 | remoteStorage server. To mitigate this, applications SHOULD clearly 440 | display to which remoteStorage server they are sending the user's 441 | data. 442 | 443 | Applications could request scopes that the user did not intend to 444 | give access to. The user SHOULD always be prompted to carefully 445 | review which scopes an application is requesting. 446 | 447 | An application may upload malicious html pages and then trick the 448 | user into visiting them, or upload malicious client-side scripts, 449 | that take advantage of being hosted on the user's domain name. The 450 | origin on which the remoteStorage server has its interface SHOULD 451 | therefore NOT be used for anything else, and the user SHOULD be 452 | warned not to visit any web pages on that origin. In particular, the 453 | OAuth dialog and launch dashboard or token revokation interface 454 | SHOULD be on a different origin than the remoteStorage interface. 455 | 456 | Where the use of bearer tokens is impractical, a user may choose to 457 | store documents on hard-to-guess URLs whose path after 458 | starts with '/public/', while sharing this URL only 459 | with the intended audience. That way, only parties who know the 460 | 461 | document's hard-to-guess URL, can access it. The server SHOULD 462 | therefore make an effort to detect and stop brute-force attacks that 463 | attempt to guess the location of such documents. 464 | The server SHOULD also detect and stop denial-of-service attacks 465 | that aim to overwhelm its interface with too much traffic. 466 | 467 | 13. IANA Considerations 468 | 469 | This document registers the 'remotestorage' link relation. 470 | 471 | 14. Acknowledgements 472 | 473 | The authors would like to thank everybody who contributed to the 474 | development of this protocol, including Kenny Bentley, Javier Diaz, 475 | Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter 476 | Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph 477 | Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin 478 | Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco 479 | Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick 480 | Jennings, Markus Sabadello, and Steven te Brinke, among many others. 481 | 482 | 483 | 484 | de Jong [Page 9] 485 | 486 | Internet-Draft remoteStorage June 2013 487 | 488 | 489 | 15. References 490 | 491 | 15.1. Normative References 492 | 493 | [WORDS] 494 | Bradner, S., "Key words for use in RFCs to Indicate Requirement 495 | Levels", BCP 14, RFC 2119, March 1997. 496 | 497 | [IRI] 498 | Duerst, M., "Internationalized Resource Identifiers (IRIs)", 499 | RFC 3987, January 2005. 500 | 501 | [WEBFINGER] 502 | Jones, Paul E., Salguerio, Gonzalo, and Smarr, Joseph, 503 | "WebFinger", draft-ietf-appsawg-webfinger-14, Work in Progress 504 | 505 | [OAUTH] 506 | "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth 507 | 2.0 Authorization Framework", RFC6749, October 2012. 508 | 509 | 15.2. Informative References 510 | 511 | [HTTPS] 512 | Rescorla, E., "HTTP Over TLS", RFC2818, May 2000. 513 | 514 | [HTTP] 515 | Fielding et al., "Hypertext Transfer Protocol -- HTTP/1.1", 516 | RFC2616, June 1999. 517 | 518 | [CORS] 519 | van Kesteren, Anne (ed), "Cross-Origin Resource Sharing -- W3C 520 | Working Draft 3 April 2012", 521 | http://www.w3.org/TR/2012/WD-cors-20120403/CORS, April 2012. 522 | 523 | [MANIFEST] 524 | Mozilla Developer Network (ed), "App manifest -- Revision 525 | 330541", https://developer.mozilla.org/en- 526 | US/docs/Apps/Manifest$revision/330541, November 2012. 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | de Jong [Page 9] 538 | 539 | Internet-Draft remoteStorage June 2013 540 | 541 | 542 | 16. Authors' addresses 543 | 544 | Michiel B. de Jong 545 | (independent) 546 | 547 | Email: michiel@michielbdejong.com 548 | 549 | 550 | F. Kooman 551 | SURFnet bv 552 | Postbus 19035 553 | 3501 DA Utrecht 554 | The Netherlands 555 | 556 | Email: Francois.Kooman@surfnet.nl 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | de Jong [Page 10] 591 | -------------------------------------------------------------------------------- /release/draft-dejong-remotestorage-02.txt: -------------------------------------------------------------------------------- 1 | INTERNET DRAFT Michiel B. de Jong 2 | Document: draft-dejong-remotestorage-02 (independent) 3 | F. Kooman 4 | Intended Status: Proposed Standard (independent) 5 | Expires: 13 June 2014 10 December 2013 6 | 7 | 8 | remoteStorage 9 | 10 | Abstract 11 | 12 | This draft describes a protocol by which client-side applications, 13 | running inside a web browser, can communicate with a data storage 14 | server that is hosted on a different domain name. This way, the 15 | provider of a web application need not also play the role of data 16 | storage provider. The protocol supports storing, retrieving, and 17 | removing individual documents, as well as listing the contents of an 18 | individual directory, and access control is based on bearer tokens. 19 | 20 | Status of this Memo 21 | 22 | This Internet-Draft is submitted in full conformance with the 23 | provisions of BCP 78 and BCP 79. 24 | 25 | Internet-Drafts are working documents of the Internet Engineering 26 | Task Force (IETF). Note that other groups may also distribute 27 | working documents as Internet-Drafts. The list of current Internet- 28 | Drafts is at http://datatracker.ietf.org/drafts/current/. 29 | 30 | Internet-Drafts are draft documents valid for a maximum of six months 31 | and may be updated, replaced, or obsoleted by other documents at any 32 | time. It is inappropriate to use Internet-Drafts as reference 33 | material or to cite them other than as "work in progress." 34 | 35 | This Internet-Draft will expire on 13 June 2014. 36 | 37 | Copyright Notice 38 | 39 | Copyright (c) 2013 IETF Trust and the persons identified as the 40 | document authors. All rights reserved. 41 | 42 | This document is subject to BCP 78 and the IETF Trust's Legal 43 | Provisions Relating to IETF Documents 44 | (http://trustee.ietf.org/license-info) in effect on the date of 45 | publication of this document. Please review these documents 46 | carefully, as they describe your rights and restrictions with respect 47 | to this document. Code Components extracted from this document must 48 | include Simplified BSD License text as described in Section 4.e of 49 | the Trust Legal Provisions and are provided without warranty as 50 | described in the Simplified BSD License. 51 | 52 | 53 | de Jong [Page 1] 54 | 55 | Internet-Draft remoteStorage December 2013 56 | 57 | 58 | Table of Contents 59 | 60 | 1. Introduction...................................................2 61 | 2. Terminology....................................................3 62 | 3. Storage model..................................................3 63 | 4. Requests.......................................................4 64 | 5. Response codes.................................................6 65 | 6. Versioning.....................................................7 66 | 7. CORS headers...................................................7 67 | 8. Session description............................................8 68 | 9. Bearer tokens and access control...............................8 69 | 10. Application-first bearer token issuance........................9 70 | 11. Storage-first bearer token issuance...........................10 71 | 12. Example wire transcripts......................................11 72 | 12.1. WebFinger................................................11 73 | 12.2. OAuth dialog form........................................12 74 | 12.3. OAuth dialog form submission.............................13 75 | 12.4. OPTIONS preflight........................................13 76 | 12.5. Initial PUT..............................................14 77 | 12.6. Subsequent PUT...........................................14 78 | 12.7. GET......................................................15 79 | 12.8. DELETE...................................................15 80 | 13. Distributed versioning........................................16 81 | 14. Security Considerations.......................................16 82 | 15. IANA Considerations...........................................18 83 | 16. Acknowledgments...............................................18 84 | 17. References....................................................18 85 | 17.1. Normative References.....................................18 86 | 17.2. Informative References...................................19 87 | 18. Authors' addresses............................................20 88 | 89 | 90 | 1. Introduction 91 | 92 | Many services for data storage are available over the internet. This 93 | specification describes a vendor-independent interface for such 94 | services. It is based on https, CORS and bearer tokens. The 95 | metaphor for addressing data on the storage is that of folders 96 | containing documents and subfolders. The actions the interface 97 | exposes are: 98 | 99 | * GET a folder: retrieve the names and current versions of the 100 | documents and subfolders currently contained by the folder 101 | 102 | 103 | de Jong [Page 2] 104 | 105 | Internet-Draft remoteStorage December 2013 106 | 107 | 108 | * GET a document: retrieve its content type, current version, 109 | and contents 110 | 111 | * PUT a document: store a new version, its content type, and 112 | contents, conditional on the current version 113 | 114 | * DELETE a document: remove it from the storage, conditional on 115 | the current version 116 | 117 | * HEAD a folder or document: like GET, but omitting the response 118 | body 119 | 120 | The exact details of these four actions are described in this 121 | specification. 122 | 123 | 2. Terminology 124 | 125 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 126 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 127 | document are to be interpreted as described in RFC 2119 [WORDS]. 128 | 129 | "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a 130 | general requirement are known to exist or appear to exist, and it is 131 | infeasible or impractical to enumerate all of them. However, they 132 | should not be interpreted as permitting implementors to fail to 133 | implement the general requirement when such failure would result in 134 | interoperability failure. 135 | 136 | 3. Storage model 137 | 138 | The server stores data in nodes that form a tree structure. 139 | Internal nodes are called 'folders' and leaf nodes are called 140 | 'documents'. For a folder, the server stores references to nodes 141 | contained in the folder, and it should be able to produce a list of 142 | them, with for each contained item: 143 | 144 | * item name 145 | * item type (folder or document) 146 | * current version 147 | * content type 148 | * content length 149 | 150 | 151 | 152 | 153 | de Jong [Page 3] 154 | 155 | Internet-Draft remoteStorage December 2013 156 | 157 | 158 | For a document, the server stores, and should be able to produce: 159 | 160 | * current version 161 | * content type 162 | * content length 163 | * content 164 | 165 | 4. Requests 166 | 167 | Client-to-server requests SHOULD be made over https [HTTPS], and 168 | servers SHOULD comply with HTTP/1.1 [HTTP]. Specifically, they 169 | SHOULD support chunked transfer coding on PUT requests. Servers MAY 170 | also offer an optional switch from https to SPDY [SPDY]. 171 | 172 | The root folder of the storage tree is represented by the following 173 | URL: 174 | 175 | URI_ENCODE( '/' ) 176 | 177 | Subsequently, if is the URL of a folder, then the 178 | URL of an item contained in it is: 179 | 180 | URI_ENCODE( ) 181 | 182 | for a document, or: 183 | 184 | URI_ENCODE( '/' ) 185 | 186 | for a folder. Item names MAY contain all characters except '/' and 187 | the null character, and MUST NOT have zero length. 188 | 189 | A document description is a map containing one string-valued 'ETag' 190 | field, one string-valued 'Content-Type' and one integer-valued 191 | 'Content-Length' field. They represent the document's current 192 | version, its content type, and its content length respectively. Note 193 | that content length is measured in octets (bytes), not in 194 | characters. 195 | 196 | A folder description is a map containing a string-valued 'ETag' 197 | field, representing the folder's current version. 198 | 199 | A successful GET request to a folder SHOULD be responded to with a 200 | JSON-LD [JSON-LD] document (content type 'application/json'), 201 | 202 | 203 | de Jong [Page 4] 204 | 205 | Internet-Draft remoteStorage December 2013 206 | 207 | 208 | containing as its 'items' field a map in which contained documents 209 | appear as entries to a document description, and 210 | contained folders appear as entries '/' to a folder 211 | description. It SHOULD furthermore contain an '@context' field with 212 | the value 'http://remotestorage.io/spec/folder-description'. For 213 | instance: 214 | 215 | { 216 | "@context": "http://remotestorage.io/spec/folder-description", 217 | "items": { 218 | "abc": { 219 | "ETag": "DEADBEEFDEADBEEFDEADBEEF", 220 | "Content-Type": "image/jpeg", 221 | "Content-Length": 82352 222 | }, 223 | "def/": { 224 | "ETag": "1337ABCD1337ABCD1337ABCD" 225 | } 226 | } 227 | } 228 | 229 | Empty folders are treated as non-existing, and therefore GET 230 | requests to them SHOULD be responded to with either a 404 response 231 | OR the JSON document representing an empty map ("{}"). However, an 232 | empty folder MUST NOT be listed as an item in its parent folder. 233 | 234 | Also, folders SHOULD be created silently, as necessary to contain 235 | newly added items. This way, PUT and DELETE requests only need to be 236 | made to documents, and folder management becomes an implicit result. 237 | 238 | A successful GET request to a document SHOULD be responded to with 239 | the full document contents in the body, the document's content type 240 | in a 'Content-Type' header, its content length in octets (not in 241 | characters) in a 'Content-Length' header, and the document's current 242 | version in an 'ETag' header. Content-Range headers on GET requests 243 | MAY be supported by the server [HTTP]. 244 | 245 | A successful PUT request to a document MUST result in: 246 | 247 | * the request body being stored as the document's new content, 248 | * parent and further ancestor folders being silently created as 249 | necessary, with the document (name and version) being added to 250 | its parent folder, and each folder added to its subsequent 251 | 252 | 253 | de Jong [Page 5] 254 | 255 | Internet-Draft remoteStorage December 2013 256 | 257 | 258 | parent, 259 | * the value of its Content-Type header being stored as the 260 | document's new content type, 261 | * its version being updated, as well as that of its parent folder 262 | and further ancestor folders, using a strong validator [HTTP, 263 | section 13.3.3]. 264 | 265 | The response MUST contain a strong ETag header, with the document's 266 | new version (for instance a hash of its contents) as its value. 267 | 268 | A successful DELETE request to a document MUST result in: 269 | 270 | * the deletion of that document from the storage, and from its 271 | parent folder, 272 | * silent deletion of the parent folder if it is left empty by 273 | this, and so on for further ancestor folders, 274 | * the version of its parent folder being updated, as well as that 275 | of further ancestor folders. 276 | 277 | A successful OPTIONS request SHOULD be responded to as described in 278 | the CORS section below. 279 | 280 | A successful HEAD request SHOULD be responded to like to the 281 | equivalent GET request, but omitting the response body. 282 | 283 | 5. Response codes 284 | 285 | Response codes SHOULD be given as defined by [HTTP, section 10] and 286 | [BEARER, section 3.1]. The following is a non-normative checklist 287 | of status codes that are likely to occur in practice: 288 | 289 | * 500 if an internal server error occurs, 290 | * 429 if the client makes too frequent requests or is suspected 291 | of malicious activity, 292 | * 414 if the request URI is too long, 293 | * 416 if Range requests are supported by the server and the Range 294 | request can not be satisfied, 295 | * 401 for all requests that don't have a bearer token with 296 | sufficient permissions, 297 | * 404 for all DELETE and GET requests to nodes that do not exist 298 | on the storage, 299 | * 304 for a conditional GET request whose pre-condition 300 | fails (see "Versioning" below), 301 | 302 | 303 | de Jong [Page 6] 304 | 305 | Internet-Draft remoteStorage December 2013 306 | 307 | 308 | * 409 for a PUT request where any folder name in the path 309 | clashes with an existing document's name at the same 310 | level, or where the document name coincides with an 311 | existing folder's name at the same level. 312 | * 412 for a conditional PUT or DELETE request whose pre-condition 313 | fails (see "Versioning" below), 314 | * 507 in case the user's account is over its storage quota, 315 | * 4xx for all malformed requests (e.g. foreign characters in the 316 | path), as well as for all PUT and DELETE requests to 317 | folders, 318 | * 2xx for all successful requests. 319 | 320 | Clients SHOULD also handle the case where a response takes too long 321 | to arrive, or where no response is received at all. 322 | 323 | 6. Versioning 324 | 325 | All successful requests MUST return an 'Expires: 0' header, and an 326 | 'ETag' header [HTTP] with, in the case of GET, the current version, 327 | in the case of PUT, the new version, and in case of DELETE, the 328 | version that was deleted. PUT and DELETE requests MAY have an 329 | 'If-Match' request header [HTTP], and MUST fail with a 412 response 330 | code if that doesn't match the document's current version. 331 | 332 | GET requests MAY have a comma-separated list of revisions in an 333 | 'If-None-Match' header [HTTP], and SHOULD be responded to with a 412 334 | response if that list includes the document or folder's current 335 | version. A PUT request MAY have an 'If-None-Match: *' header [HTTP], 336 | in which case it MUST fail with a 412 response code if the document 337 | already exists. 338 | 339 | In all 'ETag', 'If-Match' and 'If-None-Match' headers, revision 340 | strings should appear inside double quotes ("). 341 | 342 | A provider MAY offer version rollback functionality to its users, 343 | but this specification does not define the user interface for that. 344 | 345 | 7. CORS headers 346 | 347 | All responses MUST carry CORS headers [CORS]. The server MUST also 348 | reply to OPTIONS requests as per CORS. For GET requests, a wildcard 349 | origin MAY be returned, but for PUT and DELETE requests, the 350 | response MUST echo back the Origin header sent by the client. 351 | 352 | 353 | de Jong [Page 7] 354 | 355 | Internet-Draft remoteStorage December 2013 356 | 357 | 358 | 8. Session description 359 | 360 | The information that a client needs to receive in order to be able 361 | to connect to a server SHOULD reach the client as described in the 362 | 'bearer token issuance' sections below. It consists of: 363 | 364 | * , consisting of 'https://' followed by a server 365 | host, and optionally a server port and a path prefix as per 366 | [IRI]. Examples: 367 | * 'https://example.com' (host only) 368 | * 'https://example.com:8080' (host and port) 369 | * 'https://example.com/path/to/storage' (host, port and 370 | path prefix; note there is no trailing slash) 371 | * as per [OAUTH]. The token SHOULD be hard to 372 | guess and SHOULD NOT be reused from one client to another. It 373 | can however be reused in subsequent interactions with the same 374 | client, as long as that client is still trusted. Example: 375 | * 'ofb24f1ac3973e70j6vts19qr9v2eei' 376 | * , always 'draft-dejong-remotestorage-02' for this 377 | alternative version of the specification. 378 | 379 | The client can make its requests using https with CORS and bearer 380 | tokens, to the URL that is the concatenation of with 381 | '/' plus one or more '/' strings indicating a path in the 382 | folder tree, followed by zero or one strings, indicating 383 | a document. For example, if is 384 | "https://storage.example.com/bob", then to retrieve the folder 385 | contents of the /public/documents/ folder, or to retrieve a 386 | 'draft.txt' document from that folder, the client would make 387 | requests to, respectively: 388 | 389 | * https://storage.example.com/bob/public/documents/ 390 | * https://storage.example.com/bob/public/documents/draft.txt 391 | 392 | 9. Bearer tokens and access control 393 | 394 | A bearer token represents one or more access scopes. These access 395 | scopes are represented as strings of the form , 396 | where the string SHOULD be lower-case alphanumerical, other 397 | than the reserved word 'public', and can be ':r' or ':rw'. 398 | The access the bearer token gives is the sum of its access scopes, 399 | with each access scope representing the following permissions: 400 | 401 | 402 | 403 | de Jong [Page 8] 404 | 405 | Internet-Draft remoteStorage December 2013 406 | 407 | 408 | '*:rw') any request, 409 | 410 | '*:r') any GET or HEAD request, 411 | 412 | ':rw') any requests to paths that start with 413 | '/' '/' or '/public/' '/', 414 | 415 | ':r') any GET or HEAD requests to paths that start with 416 | '/' '/' or '/public/' '/', 417 | 418 | As a special exceptions, GET requests to a document (but not a 419 | folder) whose path starts with '/public/' are always allowed. They, 420 | as well as OPTIONS requests, can be made without a bearer token. All 421 | other requests should present a bearer token with sufficient access 422 | scope, using a header of the following form (no double quotes here): 423 | 424 | Authorization: Bearer 425 | 426 | In addition, providing the access token via a HTTP query parameter 427 | for GET requests MAY be supported by the server, although its use 428 | is not recommended, due to its security deficiencies; see [BEARER, 429 | section 2.3]. 430 | 431 | 10. Application-first bearer token issuance 432 | 433 | To make a remoteStorage server available as 'the remoteStorage of 434 | at ', exactly one link of the following format SHOULD 435 | be added to the webfinger record [WEBFINGER] of at : 436 | 437 | { 438 | "href": , 439 | "rel": "remotestorage", 440 | "properties": { 441 | "http://remotestorage.io/spec/version": , 442 | "http://tools.ietf.org/html/rfc6749#section-4.2": , 443 | "http://tools.ietf.org/html/rfc6750#section-2.3": , 444 | "https://tools.ietf.org/html/rfc2616#section-14.16": 445 | } 446 | } 447 | 448 | Here and are as per "Session 449 | description" above, and SHOULD be a URL where an 450 | OAuth 2.0 implicit-grant flow dialog [OAUTH] is presented, so the 451 | 452 | 453 | de Jong [Page 9] 454 | 455 | Internet-Draft remoteStorage December 2013 456 | 457 | 458 | user can supply their credentials (how, is out of scope), and allow 459 | or reject a request by the connecting application to obtain a bearer 460 | token for a certain list of access scopes. 461 | 462 | The variable SHOULD have the boolean value true if 463 | the server supports passing the bearer token in the URI query 464 | parameter as per section 2.3 of [BEARER], and false otherwise. 465 | 466 | The variable SHOULD have a string value of "GET" if 467 | Content-Range headers are supported for GET requests as per 468 | [HTTP, section 14.16], and the boolean value false if not. 469 | 470 | The server SHOULD NOT expire bearer tokens unless they are revoked, 471 | and MAY require the user to register applications as OAuth clients 472 | before first use; if no client registration is required, then the 473 | server MAY ignore the client_id parameter in favor of relying on 474 | the redirect_uri parameter for client identification. 475 | 476 | 11. Storage-first bearer token issuance 477 | 478 | The provider MAY also present a dashboard to the user, where they 479 | have some way to add open web app manifests [MANIFEST]. Adding a 480 | manifest to the dashboard is considered equivalent to clicking 481 | 'accept' in the dialog of the application-first flow. Removing one 482 | is considered equivalent to revoking its access token. 483 | 484 | As an equivalent to OAuth's 'scope' parameter, a 'remotestorage' 485 | field SHOULD be present in the root of such an application manifest 486 | document, as a JSON array of strings, each string being one access 487 | scope of the form . 488 | 489 | When the user gestures they want to use a certain application whose 490 | manifest is present on the dashboard, the dashboard SHOULD redirect 491 | to the application or open it in a new window. To mimic coming back 492 | from the OAuth dialog, it MAY add 'access_token' and 'scope' 493 | parameters to the URL fragment. 494 | 495 | Regardless of whether 'access_token' and 'scope' are specified, it 496 | SHOULD add a 'remotestorage' parameter to the URL fragment, with a 497 | value of the form '@' . When the application detects 498 | this parameter, it SHOULD resolve the webfinger record for at 499 | and extract the and information. 500 | 501 | 502 | 503 | de Jong [Page 10] 504 | 505 | Internet-Draft remoteStorage December 2013 506 | 507 | 508 | If no access_token was given, then the application SHOULD also 509 | extract the information from webfinger, and continue 510 | as per application-first bearer token issuance. 511 | 512 | Note that whereas a remoteStorage server SHOULD offer support of the 513 | application-first flow with webfinger and OAuth, it MAY choose not 514 | to support the storage-first flow, provided that users will easily 515 | remember their '@' webfinger address at that provider. 516 | Applications SHOULD, however, support both flows, which means 517 | checking the URL for a 'remotestorage' parameter, but giving the 518 | user a way to specify their webfinger address if there is none. 519 | 520 | If a server provides an application manifest dashboard, then it 521 | SHOULD merge the list of applications there with the list of 522 | issued access tokens as specified by OAuth into one list. Also, 523 | the interface for revoking an access token as specified by OAuth 524 | SHOULD coincide with removing an application from the dashboard. 525 | 526 | 12. Example wire transcripts 527 | 528 | The following examples are not normative ("\" indicates a line was 529 | wrapped). 530 | 531 | 12.1. WebFinger 532 | 533 | In application-first, an in-browser application might issue the 534 | following request, using XMLHttpRequest and CORS: 535 | 536 | GET /.well-known/webfinger?resource=acct:michiel@michielbdejon\ 537 | g.com HTTP/1.1 538 | Host: michielbdejong.com 539 | 540 | and the server's response might look like this: 541 | 542 | HTTP/1.1 200 OK 543 | Access-Control-Allow-Origin: * 544 | Access-Control-Allow-Methods: GET 545 | Access-Control-Allow-Headers: If-Match, If-None-Match 546 | Access-Control-Expose-Headers: ETag, Content-Type, Content-Len\ 547 | gth 548 | 549 | { 550 | "links":[{ 551 | 552 | 553 | de Jong [Page 11] 554 | 555 | Internet-Draft remoteStorage December 2013 556 | 557 | 558 | "href": "https://michielbdejong.com:7678/inbox", 559 | "rel": "post-me-anything" 560 | }, { 561 | "href": "https://michielbdejong.com/me.jpg", 562 | "rel": "avatar" 563 | }, { 564 | "href": "https://3pp.io:4439/storage/michiel", 565 | "rel": "remotestorage", 566 | "properties": { 567 | "http://remotestorage.io/spec/version": "draft-dejong-re\ 568 | motestorage-02", 569 | "http://tools.ietf.org/html/rfc6750#section-4.2": "https\ 570 | ://3pp.io:4439/oauth/michiel", 571 | "http://tools.ietf.org/html/rfc6750#section-2.3": false, 572 | "http://tools.ietf.org/html/rfc2616#section-14.16": false 573 | } 574 | }] 575 | } 576 | 577 | 12.2. OAuth dialog form 578 | 579 | Once the in-browser application has discovered the server's OAuth 580 | end-point, it will typically redirect the user to this URL, in 581 | order to obtain a bearer token. Say the application is hosted on 582 | https://drinks-unhosted.5apps.com/ and wants read-write access to 583 | the user's "myfavoritedrinks" scope: 584 | 585 | GET /oauth/michiel?redirect_uri=https%3A%2F%2Fdrinks-unhosted.5\ 586 | apps.com%2F&scope=myfavoritedrinks%3Arw&client_id=https%3A%2F%2Fdrinks-\ 587 | unhosted.5apps.com&response_type=token HTTP/1.1 588 | Host: 3pp.io 589 | 590 | The server's response might look like this (truncated for brevity): 591 | 592 | HTTP/1.1 200 OK 593 | 594 | 595 | 596 | 597 | Allow access? 598 | ... 599 | 600 | 601 | 602 | 603 | de Jong [Page 12] 604 | 605 | Internet-Draft remoteStorage December 2013 606 | 607 | 608 | 12.3. OAuth dialog form submission 609 | 610 | When the user submits the form, the request would look something 611 | like this: 612 | 613 | POST /oauth HTTP/1.1 614 | Host: 3pp.io:4439 615 | Origin: https://3pp.io:4439 616 | Content-Type: application/x-www-form-urlencoded 617 | Referer: https://3pp.io:4439/oauth/michiel?redirect_uri=https%3\ 618 | A%2F%2Fdrinks-unhosted.5apps.com%2F&scope=myfavoritedrinks%3Arw&client_\ 619 | id=https%3A%2F%2Fdrinks-unhosted.5apps.com&response_type=token 620 | 621 | client_id=https%3A%2F%2Fdrinks-unhosted.5apps.com&redirect_uri=\ 622 | https%3A%2F%2Fdrinks-unhosted.5apps.com%2F&response_type=token&scope=my\ 623 | favoritedrinks%3Arw&state=&username=michiel&password=something&allow=Al\ 624 | low 625 | 626 | To which the server could respond with a 302 redirect, back to the 627 | origin of the requesting application: 628 | 629 | HTTP/1.1 200 OK 630 | Location:https://drinks-unhosted.5apps.com/#access_token=j2YnGt\ 631 | XjzzzHNjkd1CJxoQubA1o%3D&token_type=bearer&state= 632 | 633 | 12.4. OPTIONS preflight 634 | 635 | When an in-browser application makes a cross-origin request which 636 | may affect the server-state, the browser will make a preflight 637 | request first, with the OPTIONS verb, for instance: 638 | 639 | OPTIONS /storage/michiel/myfavoritedrinks/ HTTP/1.1 640 | Host: 3pp.io:4439 641 | Access-Control-Request-Method: GET 642 | Origin: https://drinks-unhosted.5apps.com 643 | Access-Control-Request-Headers: Authorization 644 | Referer: https://drinks-unhosted.5apps.com/ 645 | 646 | To which the server can for instance respond: 647 | 648 | HTTP/1.1 200 OK 649 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 650 | Access-Control-Allow-Methods: GET, PUT, DELETE 651 | 652 | 653 | de Jong [Page 13] 654 | 655 | Internet-Draft remoteStorage December 2013 656 | 657 | 658 | Access-Control-Allow-Headers: Authorization, Content-Length, Co\ 659 | ntent-Type, Origin, X-Requested-With, If-Match, If-None-Match 660 | 661 | 12.5. Initial PUT 662 | 663 | An initial PUT may contain an 'If-None-Match: *' header, like this: 664 | 665 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 666 | Host: 3pp.io:4439 667 | Content-Length: 91 668 | Origin: https://drinks-unhosted.5apps.com 669 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 670 | Content-Type: application/json; charset=UTF-8 671 | Referer: https://drinks-unhosted.5apps.com/? 672 | If-None-Match: * 673 | 674 | {"name":"test","@context":"http://remotestorage.io/spec/modules\ 675 | /myfavoritedrinks/drink"} 676 | 677 | And the server may respond with either a 201 Created or a 200 OK 678 | status: 679 | 680 | HTTP/1.1 201 Created 681 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 682 | ETag: "1382694045000" 683 | 684 | 12.6. Subsequent PUT 685 | 686 | A subsequent PUT may contain an 'If-Match' header referring to the 687 | ETag previously returned, like this: 688 | 689 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 690 | Host: 3pp.io:4439 691 | Content-Length: 91 692 | Origin: https://drinks-unhosted.5apps.com 693 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 694 | Content-Type: application/json; charset=UTF-8 695 | Referer: https://drinks-unhosted.5apps.com/? 696 | If-Match: "1382694045000" 697 | 698 | {"name":"test", "updated":true, "@context":"http://remotestorag\ 699 | e.io/spec/modules/myfavoritedrinks/drink"} 700 | 701 | 702 | 703 | de Jong [Page 14] 704 | 705 | Internet-Draft remoteStorage December 2013 706 | 707 | 708 | And the server may respond with a 412 Conflict or a 200 OK status: 709 | 710 | HTTP/1.1 200 OK 711 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 712 | ETag: "1382694048000" 713 | 714 | 12.7. GET 715 | 716 | A GET request would also include the bearer token, and optionally 717 | an If-None-Match header: 718 | 719 | GET /storage/michiel/myfavoritedrinks/test HTTP/1.1 720 | Host: 3pp.io:4439 721 | Origin: https://drinks-unhosted.5apps.com 722 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 723 | Referer: https://drinks-unhosted.5apps.com/? 724 | If-None-Match: "1382694045000", "1382694048000" 725 | 726 | {"name":"test", "updated":true, "@context":"http://remotestora\ 727 | ge.io/spec/modules/myfavoritedrinks/drink"} 728 | 729 | And the server may respond with a 304 Not Modified or a 200 OK 730 | status: 731 | 732 | HTTP/1.1 304 Not Modified 733 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 734 | ETag: "1382694048000" 735 | 736 | 12.8. DELETE 737 | 738 | A DELETE request may look like this: 739 | 740 | DELETE /storage/michiel/myfavoritedrinks/test HTTP/1.1 741 | Host: 3pp.io:4439 742 | Origin: https://drinks-unhosted.5apps.com 743 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 744 | Content-Type: application/json; charset=UTF-8 745 | Referer: https://drinks-unhosted.5apps.com/? 746 | If-Match: "1382694045000" 747 | 748 | And the server may respond with a 412 Conflict or a 200 OK status: 749 | 750 | HTTP/1.1 412 Conflict 751 | 752 | 753 | de Jong [Page 15] 754 | 755 | Internet-Draft remoteStorage December 2013 756 | 757 | 758 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 759 | ETag: "1382694048000" 760 | 761 | 13. Distributed versioning 762 | 763 | This section is non-normative, and is intended to explain some of 764 | the design choices concerning ETags and folder listings. At the 765 | same time it will hopefully help readers who intend to develop an 766 | application that uses remoteStorage as its per-user data storage. 767 | When multiple clients have read/write access to the same document, 768 | versioning conflicts may occur. For instance, client A may make 769 | a PUT request that changes the document from version 1 to version 770 | 2, after which client B may make a PUT request attempting to change 771 | the same document from version 1 to version 3. 772 | 773 | In this case, client B can add an 'If-Match: "1"' header, which 774 | would trigger a 412 Conflict response code, since the current 775 | version ("2") does not match the version required as a condition by 776 | the header If-Match header ("1"). 777 | 778 | Client B is now aware of the conflict, and may consult the user, 779 | saying the update to version 3 failed. The user may then choose, 780 | through the user interface of client B, whether version 2 or 781 | version 3 should be kept, or maybe the document should be reverted 782 | on the server to version 1, or a merged version 4 is needed. Client 783 | B may then make a request that puts the document to the version the 784 | user wishes; this time setting an 'If-Match: "2"' header instead. 785 | 786 | Both client A and client B would periodically poll the root 787 | directory of each scope they have access to, to see if the version 788 | of the root directory changed. If it did, then one of the versions 789 | listed in there will necessarily have changed, and the client can 790 | make a GET request to that child directory or document, to obtain 791 | its latest version. 792 | 793 | Because an update in a document will result in a version change of 794 | its containing folder, and that change will propagate all the way 795 | to the root folder, it is not necessary to poll each document for 796 | changes individually. 797 | 798 | As an example, the root folder may contain 10 directories, 799 | each of which contain 10 directories, which each contain 10 800 | documents, so their paths would be for instance '/0/0/1', '/0/0/2', 801 | 802 | 803 | de Jong [Page 16] 804 | 805 | Internet-Draft remoteStorage December 2013 806 | 807 | 808 | etcetera. Then one GET request to the root folder '/' will be 809 | enough to know if any of these 1000 documents has changed. 810 | 811 | Say document '/7/9/2' has changed; then the GET request to '/' will 812 | come back with a different ETag, and entry '7/' will have a 813 | different value in its JSON content. The client could then request 814 | '/7/', '/7/9/', and '/7/9/2' to narrow down the one document that 815 | caused the root directory's ETag to change. 816 | 817 | Note that the remoteStorage server does not get involved in the 818 | conflict resolution. It keeps the canonical current version at all 819 | times, and allows clients to make conditional GET and PUT requests, 820 | but it is up to whichever client discovers a given version 821 | conflict, to resolve it. 822 | 823 | 14. Security Considerations 824 | 825 | To prevent man-in-the-middle attacks, the use of https instead of 826 | http is important for both the interface itself and all end-points 827 | involved in webfinger, OAuth, and (if present) the storage-first 828 | application launch dashboard. 829 | 830 | A malicious party could link to an application, but specifying a 831 | remoteStorage user address that it controls, thus tricking the user 832 | into using a trusted application to send sensitive data to the wrong 833 | remoteStorage server. To mitigate this, applications SHOULD clearly 834 | display to which remoteStorage server they are sending the user's 835 | data. 836 | 837 | Applications could request scopes that the user did not intend to 838 | give access to. The user SHOULD always be prompted to carefully 839 | review which scopes an application is requesting. 840 | 841 | An application may upload malicious html pages and then trick the 842 | user into visiting them, or upload malicious client-side scripts, 843 | that take advantage of being hosted on the user's domain name. The 844 | origin on which the remoteStorage server has its interface SHOULD 845 | therefore NOT be used for anything else, and the user SHOULD be 846 | warned not to visit any web pages on that origin. In particular, the 847 | OAuth dialog and launch dashboard or token revokation interface 848 | SHOULD be on a different origin than the remoteStorage interface. 849 | 850 | Where the use of bearer tokens is impractical, a user may choose to 851 | 852 | 853 | de Jong [Page 17] 854 | 855 | Internet-Draft remoteStorage December 2013 856 | 857 | 858 | store documents on hard-to-guess URLs whose path after 859 | starts with '/public/', while sharing this URL only 860 | with the intended audience. That way, only parties who know the 861 | document's hard-to-guess URL, can access it. The server SHOULD 862 | therefore make an effort to detect and stop brute-force attacks that 863 | attempt to guess the location of such documents. 864 | 865 | The server SHOULD also detect and stop denial-of-service attacks 866 | that aim to overwhelm its interface with too much traffic. 867 | 868 | 15. IANA Considerations 869 | 870 | This document registers the 'remotestorage' link relation, as well 871 | as the following WebFinger properties: 872 | * "http://remotestorage.io/spec/version" 873 | * "http://tools.ietf.org/html/rfc6749#section-4.2" 874 | * "http://tools.ietf.org/html/rfc6750#section-2.3" 875 | * "https://tools.ietf.org/html/rfc2616#section-14.16 876 | 877 | 16. Acknowledgements 878 | 879 | The authors would like to thank everybody who contributed to the 880 | development of this protocol, including Kenny Bentley, Javier Diaz, 881 | Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter 882 | Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph 883 | Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin 884 | Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco 885 | Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick 886 | Jennings, Markus Sabadello, Steven te Brinke, Matthias Treydte, 887 | Rick van Rein, Mark Nottingham, Julian Reschke, and Markus 888 | Lanthaler, among many others. 889 | 890 | 17. References 891 | 892 | 17.1. Normative References 893 | 894 | [WORDS] 895 | Bradner, S., "Key words for use in RFCs to Indicate Requirement 896 | Levels", BCP 14, RFC 2119, March 1997. 897 | 898 | [IRI] 899 | Duerst, M., "Internationalized Resource Identifiers (IRIs)", 900 | RFC 3987, January 2005. 901 | 902 | 903 | de Jong [Page 18] 904 | 905 | Internet-Draft remoteStorage December 2013 906 | 907 | 908 | [WEBFINGER] 909 | Jones, P., Salguerio, G., Jones, M, and Smarr, J., 910 | "WebFinger", RFC7033, September 2013. 911 | 912 | [OAUTH] 913 | "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth 914 | 2.0 Authorization Framework", RFC6749, October 2012. 915 | 916 | 17.2. Informative References 917 | 918 | [HTTPS] 919 | Rescorla, E., "HTTP Over TLS", RFC2818, May 2000. 920 | [HTTP] 921 | Fielding et al., "Hypertext Transfer Protocol -- HTTP/1.1", 922 | RFC2616, June 1999. 923 | 924 | [SPDY] 925 | Mark Belshe, Roberto Peon, "SPDY Protocol - Draft 3.1", http:// 926 | www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1, 927 | September 2013. 928 | 929 | [JSON-LD] 930 | M. Sporny, G. Kellogg, M. Lanthaler, "JSON-LD 1.0", W3C 931 | Proposed Recommendation, 932 | http://www.w3.org/TR/2013/PR-json-ld-20131105/, November 2013. 933 | 934 | [CORS] 935 | van Kesteren, Anne (ed), "Cross-Origin Resource Sharing -- 936 | W3C Candidate Recommendation 29 January 2013", 937 | http://www.w3.org/TR/cors/, January 2013. 938 | 939 | [MANIFEST] 940 | Mozilla Developer Network (ed), "App manifest -- Revision 941 | 330541", https://developer.mozilla.org/en- 942 | US/Apps/Developing/Manifest$revision/482369, October 2013. 943 | 944 | [BEARER] 945 | M. Jones, D. Hardt, "The OAuth 2.0 Authorization Framework: 946 | Bearer Token Usage", RFC6750, 947 | http://tools.ietf.org/html/rfc6750#section-2.3, October 2012. 948 | 949 | 950 | 951 | 952 | 953 | de Jong [Page 19] 954 | 955 | Internet-Draft remoteStorage December 2013 956 | 957 | 958 | 18. Authors' addresses 959 | 960 | Michiel B. de Jong 961 | (independent) 962 | 963 | Email: michiel@michielbdejong.com 964 | 965 | 966 | F. Kooman 967 | (independent) 968 | 969 | Email: fkooman@tuxed.net 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | de Jong [Page 20] 1004 | -------------------------------------------------------------------------------- /release/draft-dejong-remotestorage-03.txt: -------------------------------------------------------------------------------- 1 | INTERNET DRAFT Michiel B. de Jong 2 | Document: draft-dejong-remotestorage-03 (independent) 3 | F. Kooman 4 | Intended Status: Proposed Standard (independent) 5 | Expires: 15 December 2014 13 June 2014 6 | 7 | 8 | remoteStorage 9 | 10 | Abstract 11 | 12 | This draft describes a protocol by which client-side applications, 13 | running inside a web browser, can communicate with a data storage 14 | server that is hosted on a different domain name. This way, the 15 | provider of a web application need not also play the role of data 16 | storage provider. The protocol supports storing, retrieving, and 17 | removing individual documents, as well as listing the contents of an 18 | individual folder, and access control is based on bearer tokens. 19 | 20 | Status of this Memo 21 | 22 | This Internet-Draft is submitted in full conformance with the 23 | provisions of BCP 78 and BCP 79. 24 | 25 | Internet-Drafts are working documents of the Internet Engineering 26 | Task Force (IETF). Note that other groups may also distribute 27 | working documents as Internet-Drafts. The list of current Internet- 28 | Drafts is at http://datatracker.ietf.org/drafts/current/. 29 | 30 | Internet-Drafts are draft documents valid for a maximum of six months 31 | and may be updated, replaced, or obsoleted by other documents at any 32 | time. It is inappropriate to use Internet-Drafts as reference 33 | material or to cite them other than as "work in progress." 34 | 35 | This Internet-Draft will expire on 15 December 2014. 36 | 37 | Copyright Notice 38 | 39 | Copyright (c) 2013 IETF Trust and the persons identified as the 40 | document authors. All rights reserved. 41 | 42 | This document is subject to BCP 78 and the IETF Trust's Legal 43 | Provisions Relating to IETF Documents 44 | (http://trustee.ietf.org/license-info) in effect on the date of 45 | publication of this document. Please review these documents 46 | carefully, as they describe your rights and restrictions with respect 47 | to this document. Code Components extracted from this document must 48 | include Simplified BSD License text as described in Section 4.e of 49 | the Trust Legal Provisions and are provided without warranty as 50 | described in the Simplified BSD License. 51 | 52 | 53 | de Jong [Page 1] 54 | 55 | Internet-Draft remoteStorage June 2014 56 | 57 | 58 | Table of Contents 59 | 60 | 1. Introduction...................................................2 61 | 2. Terminology....................................................3 62 | 3. Storage model..................................................3 63 | 4. Requests.......................................................4 64 | 5. Response codes.................................................6 65 | 6. Versioning.....................................................7 66 | 7. CORS headers...................................................8 67 | 8. Session description............................................8 68 | 9. Bearer tokens and access control...............................9 69 | 10. Application-first bearer token issuance........................9 70 | 11. Storage-first bearer token issuance...........................11 71 | 12. Example wire transcripts......................................12 72 | 12.1. WebFinger................................................12 73 | 12.2. OAuth dialog form........................................13 74 | 12.3. OAuth dialog form submission.............................13 75 | 12.4. OPTIONS preflight........................................14 76 | 12.5. Initial PUT..............................................14 77 | 12.6. Subsequent PUT...........................................15 78 | 12.7. GET......................................................15 79 | 12.8. DELETE...................................................16 80 | 13. Distributed versioning........................................16 81 | 14. Security Considerations.......................................18 82 | 15. IANA Considerations...........................................19 83 | 16. Acknowledgments...............................................19 84 | 17. References....................................................19 85 | 17.1. Normative References.....................................19 86 | 17.2. Informative References...................................19 87 | 18. Authors' addresses............................................20 88 | 89 | 90 | 1. Introduction 91 | 92 | Many services for data storage are available over the internet. This 93 | specification describes a vendor-independent interface for such 94 | services. It is based on https, CORS and bearer tokens. The 95 | metaphor for addressing data on the storage is that of folders 96 | containing documents and subfolders. The actions the interface 97 | exposes are: 98 | 99 | * GET a folder: retrieve the names and current versions of the 100 | documents and subfolders currently contained by the folder 101 | 102 | 103 | de Jong [Page 2] 104 | 105 | Internet-Draft remoteStorage June 2014 106 | 107 | 108 | * GET a document: retrieve its content type, current version, 109 | and contents 110 | 111 | * PUT a document: store a new version, its content type, and 112 | contents, conditional on the current version 113 | 114 | * DELETE a document: remove it from the storage, conditional on 115 | the current version 116 | 117 | * HEAD a folder or document: like GET, but omitting the response 118 | body 119 | 120 | The exact details of these four actions are described in this 121 | specification. 122 | 123 | 2. Terminology 124 | 125 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 126 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 127 | document are to be interpreted as described in RFC 2119 [WORDS]. 128 | 129 | "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a 130 | general requirement are known to exist or appear to exist, and it is 131 | infeasible or impractical to enumerate all of them. However, they 132 | should not be interpreted as permitting implementors to fail to 133 | implement the general requirement when such failure would result in 134 | interoperability failure. 135 | 136 | 3. Storage model 137 | 138 | The server stores data in nodes that form a tree structure. 139 | Internal nodes are called 'folders' and leaf nodes are called 140 | 'documents'. For a folder, the server stores references to nodes 141 | contained in the folder, and it should be able to produce a list of 142 | them, with for each contained item: 143 | 144 | * item name 145 | * item type (folder or document) 146 | * current version 147 | * content type 148 | * content length 149 | 150 | 151 | 152 | 153 | de Jong [Page 3] 154 | 155 | Internet-Draft remoteStorage June 2014 156 | 157 | 158 | For a document, the server stores, and should be able to produce: 159 | 160 | * current version 161 | * content type 162 | * content length 163 | * content 164 | 165 | 4. Requests 166 | 167 | Client-to-server requests SHOULD be made over https [HTTPS], and 168 | servers MUST comply with HTTP/1.1 [HTTP]. Specifically, they 169 | MUST support chunked transfer coding on PUT requests. Servers MAY 170 | also offer an optional switch from https to SPDY [SPDY]. 171 | 172 | The root folder of the storage tree is represented by the following 173 | URL: 174 | 175 | URI_ENCODE( '/' ) 176 | 177 | Subsequently, if is the URL of a folder, then the 178 | URL of an item contained in it is: 179 | 180 | URI_ENCODE( ) 181 | 182 | for a document, or: 183 | 184 | URI_ENCODE( '/' ) 185 | 186 | for a folder. Item names MAY contain all characters except '/' and 187 | the null character, and MUST NOT have zero length. 188 | 189 | A document description is a map containing one string-valued 'ETag' 190 | field, one string-valued 'Content-Type' and one integer-valued 191 | 'Content-Length' field. They represent the document's current 192 | version, its content type, and its content length respectively. Note 193 | that content length is measured in octets (bytes), not in 194 | characters. 195 | 196 | A folder description is a map containing a string-valued 'ETag' 197 | field, representing the folder's current version. 198 | 199 | A successful GET request to a folder SHOULD be responded to with a 200 | JSON-LD [JSON-LD] document (content type 'application/ld+json'), 201 | 202 | 203 | de Jong [Page 4] 204 | 205 | Internet-Draft remoteStorage June 2014 206 | 207 | 208 | containing as its 'items' field a map in which contained documents 209 | appear as entries to a document description, and 210 | contained non-empty folders appear as entries '/' to a 211 | folder description. It SHOULD also contain an '@context' field with 212 | the value 'http://remotestorage.io/spec/folder-description'. For 213 | instance: 214 | 215 | { 216 | "@context": "http://remotestorage.io/spec/folder-description", 217 | "items": { 218 | "abc": { 219 | "ETag": "DEADBEEFDEADBEEFDEADBEEF", 220 | "Content-Type": "image/jpeg", 221 | "Content-Length": 82352 222 | }, 223 | "def/": { 224 | "ETag": "1337ABCD1337ABCD1337ABCD" 225 | } 226 | } 227 | } 228 | 229 | All folders are treated as existing, and therefore GET requests to 230 | untouched folders SHOULD be responded to with a folder description 231 | with no items (the items field set to '{}'). However, an empty 232 | folder MUST NOT be listed as an item in its parent folder. 233 | 234 | Also, since folders exist automatically, PUT and DELETE requests 235 | only need to be made to documents, and never to folders. A document 236 | PUT will make all ancestor folders along its path become non-empty; 237 | deleting the last document from a subtree will make that whole 238 | subtree become empty. Folders will therefore show up in their parent 239 | folder descriptions if and only if their subtree contains at least 240 | one document. 241 | 242 | A successful GET request to a document SHOULD be responded to with 243 | the full document contents in the body, the document's content type 244 | in a 'Content-Type' header, its content length in octets (not in 245 | characters) in a 'Content-Length' header, and the document's current 246 | version as a strong ETag in an 'ETag' header. 247 | 248 | Note that the use of strong ETags prohibits changing the response 249 | body based on request headers; in particular, the server will not be 250 | able to serve the same document uncompressed to some clients and 251 | 252 | 253 | de Jong [Page 5] 254 | 255 | Internet-Draft remoteStorage June 2014 256 | 257 | 258 | gzipped when requested by the client, since the two bodies would not 259 | be identical byte-for-byte. 260 | 261 | Servers MAY support Content-Range headers [HTTP] on GET requests, 262 | but whether or not they do SHOULD be announced through the 263 | variable mentioned below in section 10. 264 | 265 | A successful PUT request to a document MUST result in: 266 | 267 | * the request body being stored as the document's new content, 268 | * parent and further ancestor folders being silently created as 269 | necessary, with the document (name and version) being added to 270 | its parent folder, and each folder added to its subsequent 271 | parent, 272 | * the value of its Content-Type header being stored as the 273 | document's new content type, 274 | * its version being updated, as well as that of its parent folder 275 | and further ancestor folders, using a strong validator [HTTP, 276 | section 13.3.3]. 277 | 278 | The response MUST contain a strong ETag header, with the document's 279 | new version (for instance a hash of its contents) as its value. 280 | 281 | A successful DELETE request to a document MUST result in: 282 | 283 | * the deletion of that document from the storage, and from its 284 | parent folder, 285 | * silent deletion of the parent folder if it is left empty by 286 | this, and so on for further ancestor folders, 287 | * the version of its parent folder being updated, as well as that 288 | of further ancestor folders. 289 | 290 | A successful OPTIONS request SHOULD be responded to as described in 291 | the CORS section below. 292 | 293 | A successful HEAD request SHOULD be responded to like to the 294 | equivalent GET request, but omitting the response body. 295 | 296 | 5. Response codes 297 | 298 | Response codes SHOULD be given as defined by [HTTP, section 10] and 299 | [BEARER, section 3.1]. The following is a non-normative checklist 300 | of status codes that are likely to occur in practice: 301 | 302 | 303 | de Jong [Page 6] 304 | 305 | Internet-Draft remoteStorage June 2014 306 | 307 | 308 | 309 | * 500 if an internal server error occurs, 310 | * 429 if the client makes too frequent requests or is suspected 311 | of malicious activity, 312 | * 414 if the request URI is too long, 313 | * 416 if Range requests are supported by the server and the Range 314 | request can not be satisfied, 315 | * 401 for all requests that don't have a bearer token with 316 | sufficient permissions, 317 | * 404 for all DELETE and GET requests to nodes that do not exist 318 | on the storage, 319 | * 304 for a conditional GET request whose pre-condition 320 | fails (see "Versioning" below), 321 | * 409 for a PUT request where any folder name in the path 322 | clashes with an existing document's name at the same 323 | level, or where the document name coincides with an 324 | existing folder's name at the same level. 325 | * 412 for a conditional PUT or DELETE request whose pre-condition 326 | fails (see "Versioning" below), 327 | * 507 in case the user's account is over its storage quota, 328 | * 4xx for all malformed requests (e.g. foreign characters in the 329 | path), as well as for all PUT and DELETE requests to 330 | folders, 331 | * 2xx for all successful requests. 332 | 333 | Clients SHOULD also handle the case where a response takes too long 334 | to arrive, or where no response is received at all. 335 | 336 | 6. Versioning 337 | 338 | All successful requests MUST return an 'Expires: 0' header, and an 339 | 'ETag' header [HTTP] with, in the case of GET, the current version, 340 | in the case of PUT, the new version, and in case of DELETE, the 341 | version that was deleted. PUT and DELETE requests MAY have an 342 | 'If-Match' request header [HTTP], and MUST fail with a 412 response 343 | code if that doesn't match the document's current version. 344 | 345 | GET requests MAY have a comma-separated list of revisions in an 346 | 'If-None-Match' header [HTTP], and SHOULD be responded to with a 412 347 | response if that list includes the document or folder's current 348 | version. A PUT request MAY have an 'If-None-Match: *' header [HTTP], 349 | in which case it MUST fail with a 412 response code if the document 350 | already exists. 351 | 352 | 353 | de Jong [Page 7] 354 | 355 | Internet-Draft remoteStorage June 2014 356 | 357 | 358 | 359 | In all 'ETag', 'If-Match' and 'If-None-Match' headers, revision 360 | strings should appear inside double quotes ("). 361 | 362 | A provider MAY offer version rollback functionality to its users, 363 | but this specification does not define the user interface for that. 364 | 365 | 7. CORS headers 366 | 367 | All responses MUST carry CORS headers [CORS]. The server MUST also 368 | reply to OPTIONS requests as per CORS. For GET requests, a wildcard 369 | origin MAY be returned, but for PUT and DELETE requests, the 370 | response MUST echo back the Origin header sent by the client. 371 | 372 | 8. Session description 373 | 374 | The information that a client needs to receive in order to be able 375 | to connect to a server SHOULD reach the client as described in the 376 | 'bearer token issuance' sections below. It consists of: 377 | 378 | * , consisting of 'https://' followed by a server 379 | host, and optionally a server port and a path prefix as per 380 | [IRI]. Examples: 381 | * 'https://example.com' (host only) 382 | * 'https://example.com:8080' (host and port) 383 | * 'https://example.com/path/to/storage' (host, port and 384 | path prefix; note there is no trailing slash) 385 | * as per [OAUTH]. The token SHOULD be hard to 386 | guess and SHOULD NOT be reused from one client to another. It 387 | can however be reused in subsequent interactions with the same 388 | client, as long as that client is still trusted. Example: 389 | * 'ofb24f1ac3973e70j6vts19qr9v2eei' 390 | * , always 'draft-dejong-remotestorage-03' for this 391 | alternative version of the specification. 392 | 393 | The client can make its requests using https with CORS and bearer 394 | tokens, to the URL that is the concatenation of with 395 | '/' plus one or more '/' strings indicating a path in the 396 | folder tree, followed by zero or one strings, indicating 397 | a document. For example, if is 398 | "https://storage.example.com/bob", then to retrieve the folder 399 | contents of the /public/documents/ folder, or to retrieve a 400 | 'draft.txt' document from that folder, the client would make 401 | 402 | 403 | de Jong [Page 8] 404 | 405 | Internet-Draft remoteStorage June 2014 406 | 407 | 408 | requests to, respectively: 409 | 410 | * https://storage.example.com/bob/public/documents/ 411 | * https://storage.example.com/bob/public/documents/draft.txt 412 | 413 | 9. Bearer tokens and access control 414 | 415 | A bearer token represents one or more access scopes. These access 416 | scopes are represented as strings of the form , 417 | where the string SHOULD be lower-case alphanumerical, other 418 | than the reserved word 'public', and can be ':r' or ':rw'. 419 | The access the bearer token gives is the sum of its access scopes, 420 | with each access scope representing the following permissions: 421 | 422 | '*:rw') any request, 423 | 424 | '*:r') any GET or HEAD request, 425 | 426 | ':rw') any requests to paths that start with 427 | '/' '/' or '/public/' '/', 428 | 429 | ':r') any GET or HEAD requests to paths that start with 430 | '/' '/' or '/public/' '/', 431 | 432 | As a special exceptions, GET requests to a document (but not a 433 | folder) whose path starts with '/public/' are always allowed. They, 434 | as well as OPTIONS requests, can be made without a bearer token. 435 | Unless [KERBEROS] is used (see section 10 below), all other requests 436 | SHOULD present a bearer token with sufficient access scope, using a 437 | header of the following form (no double quotes here): 438 | 439 | Authorization: Bearer 440 | 441 | In addition, providing the access token via a HTTP query parameter 442 | for GET requests MAY be supported by the server, although its use 443 | is not recommended, due to its security deficiencies; see [BEARER, 444 | section 2.3]. 445 | 446 | 10. Application-first bearer token issuance 447 | 448 | To make a remoteStorage server available as 'the remoteStorage of 449 | at ', exactly one link of the following format SHOULD 450 | be added to the webfinger record [WEBFINGER] of at : 451 | 452 | 453 | de Jong [Page 9] 454 | 455 | Internet-Draft remoteStorage June 2014 456 | 457 | 458 | 459 | { 460 | "href": , 461 | "rel": "remotestorage", 462 | "properties": { 463 | "http://remotestorage.io/spec/version": , 464 | "http://tools.ietf.org/html/rfc6749#section-4.2": , 465 | "http://tools.ietf.org/html/rfc6750#section-2.3": , 466 | "https://tools.ietf.org/html/rfc2616#section-14.16": 467 | } 468 | } 469 | 470 | Here and are as per "Session 471 | description" above, and SHOULD be eihter the boolean 472 | value false or a URL where an OAuth 2.0 implicit-grant flow dialog 473 | [OAUTH] is presented. 474 | 475 | If is a URL, the user can supply their credentials 476 | there (how, is out of scope), and allow or reject a request by the 477 | connecting application to obtain a bearer token for a certain list 478 | of access scopes. 479 | 480 | If is false, the client will not have a way to obtain 481 | an access token, and SHOULD send all requests without Authorization 482 | header, and rely on Kerberos [KERBEROS] instead for requests that 483 | would normally be sent with a bearer token, but servers SHOULD NOT 484 | impose any such access barriers for resources that would normally 485 | not require an access token. 486 | 487 | The variable SHOULD have the boolean value true if 488 | the server supports passing the bearer token in the URI query 489 | parameter as per section 2.3 of [BEARER], and false otherwise. 490 | 491 | The variable SHOULD have a string value of "GET" if 492 | Content-Range headers are supported for GET requests as per 493 | [HTTP, section 14.16], and the boolean value false if not. 494 | 495 | The server MAY expire bearer tokens, and MAY require the user to 496 | register applications as OAuth clients before first use; if no 497 | client registration is required, then the server MAY ignore the 498 | client_id parameter in favor of relying on the redirect_uri 499 | parameter for client identification. 500 | 501 | 502 | 503 | de Jong [Page 10] 504 | 505 | Internet-Draft remoteStorage June 2014 506 | 507 | 508 | 509 | 11. Storage-first bearer token issuance 510 | 511 | The provider MAY also present a dashboard to the user, where they 512 | have some way to add open web app manifests [MANIFEST]. Adding a 513 | manifest to the dashboard is considered equivalent to clicking 514 | 'accept' in the dialog of the application-first flow. Removing one 515 | is considered equivalent to revoking its access token. 516 | 517 | As an equivalent to OAuth's 'scope' parameter, a 'datastores-access' 518 | field SHOULD be present in the root of such an application manifest 519 | document, with entries -> '{"access": "readonly"}' for 520 | 'r' or '{"access": "readwrite"}' for 'rw', as 521 | prescribed in [DATASTORE]. 522 | 523 | When the user gestures they want to use a certain application whose 524 | manifest is present on the dashboard, the dashboard SHOULD redirect 525 | to the application or open it in a new window. To mimic coming back 526 | from the OAuth dialog, it MAY add 'access_token' and 'scope' 527 | fields to the URL fragment. 528 | 529 | Regardless of whether 'access_token' and 'scope' are specified, it 530 | SHOULD add a 'remotestorage' field to the URL fragment, with a 531 | value of the form '@' . When the application detects 532 | this parameter, it SHOULD resolve the webfinger record for at 533 | and extract the and information. 534 | 535 | If no access_token was given, then the application SHOULD also 536 | extract the information from webfinger, and continue 537 | as per application-first bearer token issuance. 538 | 539 | Note that whereas a remoteStorage server SHOULD offer support of the 540 | application-first flow with webfinger and OAuth, it MAY choose not 541 | to support the storage-first flow, provided that users will easily 542 | remember their '@' webfinger address at that provider. 543 | Applications SHOULD, however, support both flows, which means 544 | checking the URL for a 'remotestorage' parameter, but giving the 545 | user a way to specify their webfinger address if there is none. 546 | 547 | If a server provides an application manifest dashboard, then it 548 | SHOULD merge the list of applications there with the list of 549 | issued access tokens as specified by OAuth into one list. Also, 550 | the interface for revoking an access token as specified by OAuth 551 | 552 | 553 | de Jong [Page 11] 554 | 555 | Internet-Draft remoteStorage June 2014 556 | 557 | 558 | SHOULD coincide with removing an application from the dashboard. 559 | 560 | Servers MAY also provide a way to create access tokens directly from 561 | their user interface. Such functionality would be aimed mainly at 562 | developers, to manually copy and paste a token into a script or 563 | debug tool, thus bypassing the need for an OAuth dance. Clients 564 | SHOULD NOT rely on this in production. 565 | 566 | 12. Example wire transcripts 567 | 568 | The following examples are not normative ("\" indicates a line was 569 | wrapped). 570 | 571 | 12.1. WebFinger 572 | 573 | In application-first, an in-browser application might issue the 574 | following request, using XMLHttpRequest and CORS: 575 | 576 | GET /.well-known/webfinger?resource=acct:michiel@michielbdejon\ 577 | g.com HTTP/1.1 578 | Host: michielbdejong.com 579 | 580 | and the server's response might look like this: 581 | 582 | HTTP/1.1 200 OK 583 | Access-Control-Allow-Origin: * 584 | Access-Control-Allow-Methods: GET 585 | Access-Control-Allow-Headers: If-Match, If-None-Match 586 | Access-Control-Expose-Headers: ETag, Content-Type, Content-Len\ 587 | gth 588 | 589 | { 590 | "links":[{ 591 | "href": "https://michielbdejong.com:7678/inbox", 592 | "rel": "post-me-anything" 593 | }, { 594 | "href": "https://michielbdejong.com/me.jpg", 595 | "rel": "avatar" 596 | }, { 597 | "href": "https://3pp.io:4439/storage/michiel", 598 | "rel": "remotestorage", 599 | "properties": { 600 | "http://remotestorage.io/spec/version": "draft-dejong-re\ 601 | 602 | 603 | de Jong [Page 12] 604 | 605 | Internet-Draft remoteStorage June 2014 606 | 607 | 608 | motestorage-03", 609 | "http://tools.ietf.org/html/rfc6750#section-4.2": "https\ 610 | ://3pp.io:4439/oauth/michiel", 611 | "http://tools.ietf.org/html/rfc6750#section-2.3": false, 612 | "http://tools.ietf.org/html/rfc2616#section-14.16": false 613 | } 614 | }] 615 | } 616 | 617 | 12.2. OAuth dialog form 618 | 619 | Once the in-browser application has discovered the server's OAuth 620 | end-point, it will typically redirect the user to this URL, in 621 | order to obtain a bearer token. Say the application is hosted on 622 | https://drinks-unhosted.5apps.com/ and wants read-write access to 623 | the user's "myfavoritedrinks" scope: 624 | 625 | GET /oauth/michiel?redirect_uri=https%3A%2F%2Fdrinks-unhosted.5\ 626 | apps.com%2F&scope=myfavoritedrinks%3Arw&client_id=https%3A%2F%2Fdrinks-\ 627 | unhosted.5apps.com&response_type=token HTTP/1.1 628 | Host: 3pp.io 629 | 630 | The server's response might look like this (truncated for brevity): 631 | 632 | HTTP/1.1 200 OK 633 | 634 | 635 | 636 | 637 | Allow access? 638 | ... 639 | 640 | 641 | 12.3. OAuth dialog form submission 642 | 643 | When the user submits the form, the request would look something 644 | like this: 645 | 646 | POST /oauth HTTP/1.1 647 | Host: 3pp.io:4439 648 | Origin: https://3pp.io:4439 649 | Content-Type: application/x-www-form-urlencoded 650 | Referer: https://3pp.io:4439/oauth/michiel?redirect_uri=https%3\ 651 | 652 | 653 | de Jong [Page 13] 654 | 655 | Internet-Draft remoteStorage June 2014 656 | 657 | 658 | A%2F%2Fdrinks-unhosted.5apps.com%2F&scope=myfavoritedrinks%3Arw&client_\ 659 | id=https%3A%2F%2Fdrinks-unhosted.5apps.com&response_type=token 660 | 661 | client_id=https%3A%2F%2Fdrinks-unhosted.5apps.com&redirect_uri=\ 662 | https%3A%2F%2Fdrinks-unhosted.5apps.com%2F&response_type=token&scope=my\ 663 | favoritedrinks%3Arw&state=&username=michiel&password=something&allow=Al\ 664 | low 665 | 666 | To which the server could respond with a 302 redirect, back to the 667 | origin of the requesting application: 668 | 669 | HTTP/1.1 302 Found 670 | Location:https://drinks-unhosted.5apps.com/#access_token=j2YnGt\ 671 | XjzzzHNjkd1CJxoQubA1o%3D&token_type=bearer&state= 672 | 673 | 12.4. OPTIONS preflight 674 | 675 | When an in-browser application makes a cross-origin request which 676 | may affect the server-state, the browser will make a preflight 677 | request first, with the OPTIONS verb, for instance: 678 | 679 | OPTIONS /storage/michiel/myfavoritedrinks/ HTTP/1.1 680 | Host: 3pp.io:4439 681 | Access-Control-Request-Method: GET 682 | Origin: https://drinks-unhosted.5apps.com 683 | Access-Control-Request-Headers: Authorization 684 | Referer: https://drinks-unhosted.5apps.com/ 685 | 686 | To which the server can for instance respond: 687 | 688 | HTTP/1.1 200 OK 689 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 690 | Access-Control-Allow-Methods: GET, PUT, DELETE 691 | Access-Control-Allow-Headers: Authorization, Content-Length, Co\ 692 | ntent-Type, Origin, X-Requested-With, If-Match, If-None-Match 693 | 694 | 12.5. Initial PUT 695 | 696 | An initial PUT may contain an 'If-None-Match: *' header, like this: 697 | 698 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 699 | Host: 3pp.io:4439 700 | Content-Length: 91 701 | 702 | 703 | de Jong [Page 14] 704 | 705 | Internet-Draft remoteStorage June 2014 706 | 707 | 708 | Origin: https://drinks-unhosted.5apps.com 709 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 710 | Content-Type: application/json; charset=UTF-8 711 | Referer: https://drinks-unhosted.5apps.com/? 712 | If-None-Match: * 713 | 714 | {"name":"test","@context":"http://remotestorage.io/spec/modules\ 715 | /myfavoritedrinks/drink"} 716 | 717 | And the server may respond with either a 201 Created or a 200 OK 718 | status: 719 | 720 | HTTP/1.1 201 Created 721 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 722 | ETag: "1382694045000" 723 | 724 | 12.6. Subsequent PUT 725 | 726 | A subsequent PUT may contain an 'If-Match' header referring to the 727 | ETag previously returned, like this: 728 | 729 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 730 | Host: 3pp.io:4439 731 | Content-Length: 91 732 | Origin: https://drinks-unhosted.5apps.com 733 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 734 | Content-Type: application/json; charset=UTF-8 735 | Referer: https://drinks-unhosted.5apps.com/? 736 | If-Match: "1382694045000" 737 | 738 | {"name":"test", "updated":true, "@context":"http://remotestorag\ 739 | e.io/spec/modules/myfavoritedrinks/drink"} 740 | 741 | And the server may respond with a 412 Conflict or a 200 OK status: 742 | 743 | HTTP/1.1 200 OK 744 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 745 | ETag: "1382694048000" 746 | 747 | 12.7. GET 748 | 749 | A GET request would also include the bearer token, and optionally 750 | an If-None-Match header: 751 | 752 | 753 | de Jong [Page 15] 754 | 755 | Internet-Draft remoteStorage June 2014 756 | 757 | 758 | 759 | GET /storage/michiel/myfavoritedrinks/test HTTP/1.1 760 | Host: 3pp.io:4439 761 | Origin: https://drinks-unhosted.5apps.com 762 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 763 | Referer: https://drinks-unhosted.5apps.com/? 764 | If-None-Match: "1382694045000", "1382694048000" 765 | 766 | {"name":"test", "updated":true, "@context":"http://remotestora\ 767 | ge.io/spec/modules/myfavoritedrinks/drink"} 768 | 769 | And the server may respond with a 304 Not Modified or a 200 OK 770 | status: 771 | 772 | HTTP/1.1 304 Not Modified 773 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 774 | ETag: "1382694048000" 775 | 776 | 12.8. DELETE 777 | 778 | A DELETE request may look like this: 779 | 780 | DELETE /storage/michiel/myfavoritedrinks/test HTTP/1.1 781 | Host: 3pp.io:4439 782 | Origin: https://drinks-unhosted.5apps.com 783 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 784 | Content-Type: application/json; charset=UTF-8 785 | Referer: https://drinks-unhosted.5apps.com/? 786 | If-Match: "1382694045000" 787 | 788 | And the server may respond with a 412 Conflict or a 200 OK status: 789 | 790 | HTTP/1.1 412 Conflict 791 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 792 | ETag: "1382694048000" 793 | 794 | 13. Distributed versioning 795 | 796 | This section is non-normative, and is intended to explain some of 797 | the design choices concerning ETags and folder listings. At the 798 | same time it will hopefully help readers who intend to develop an 799 | application that uses remoteStorage as its per-user data storage. 800 | When multiple clients have read/write access to the same document, 801 | 802 | 803 | de Jong [Page 16] 804 | 805 | Internet-Draft remoteStorage June 2014 806 | 807 | 808 | versioning conflicts may occur. For instance, client A may make 809 | a PUT request that changes the document from version 1 to version 810 | 2, after which client B may make a PUT request attempting to change 811 | the same document from version 1 to version 3. 812 | 813 | In this case, client B can add an 'If-Match: "1"' header, which 814 | would trigger a 412 Conflict response code, since the current 815 | version ("2") does not match the version required as a condition by 816 | the header If-Match header ("1"). 817 | 818 | Client B is now aware of the conflict, and may consult the user, 819 | saying the update to version 3 failed. The user may then choose, 820 | through the user interface of client B, whether version 2 or 821 | version 3 should be kept, or maybe the document should be reverted 822 | on the server to version 1, or a merged version 4 is needed. Client 823 | B may then make a request that puts the document to the version the 824 | user wishes; this time setting an 'If-Match: "2"' header instead. 825 | 826 | Both client A and client B would periodically poll the root 827 | folder of each scope they have access to, to see if the version 828 | of the root folder changed. If it did, then one of the versions 829 | listed in there will necessarily have changed, and the client can 830 | make a GET request to that child folder or document, to obtain 831 | its latest version. 832 | 833 | Because an update in a document will result in a version change of 834 | its containing folder, and that change will propagate all the way 835 | to the root folder, it is not necessary to poll each document for 836 | changes individually. 837 | 838 | As an example, the root folder may contain 10 directories, 839 | each of which contain 10 directories, which each contain 10 840 | documents, so their paths would be for instance '/0/0/1', '/0/0/2', 841 | etcetera. Then one GET request to the root folder '/' will be 842 | enough to know if any of these 1000 documents has changed. 843 | 844 | Say document '/7/9/2' has changed; then the GET request to '/' will 845 | come back with a different ETag, and entry '7/' will have a 846 | different value in its JSON content. The client could then request 847 | '/7/', '/7/9/', and '/7/9/2' to narrow down the one document that 848 | caused the root folder's ETag to change. 849 | 850 | Note that the remoteStorage server does not get involved in the 851 | 852 | 853 | de Jong [Page 17] 854 | 855 | Internet-Draft remoteStorage June 2014 856 | 857 | 858 | conflict resolution. It keeps the canonical current version at all 859 | times, and allows clients to make conditional GET and PUT requests, 860 | but it is up to whichever client discovers a given version 861 | conflict, to resolve it. 862 | 863 | 14. Security Considerations 864 | 865 | To prevent man-in-the-middle attacks, the use of https instead of 866 | http is important for both the interface itself and all end-points 867 | involved in webfinger, OAuth, and (if present) the storage-first 868 | application launch dashboard. 869 | 870 | A malicious party could link to an application, but specifying a 871 | remoteStorage user address that it controls, thus tricking the user 872 | into using a trusted application to send sensitive data to the wrong 873 | remoteStorage server. To mitigate this, applications SHOULD clearly 874 | display to which remoteStorage server they are sending the user's 875 | data. 876 | 877 | Applications could request scopes that the user did not intend to 878 | give access to. The user SHOULD always be prompted to carefully 879 | review which scopes an application is requesting. 880 | 881 | An application may upload malicious html pages and then trick the 882 | user into visiting them, or upload malicious client-side scripts, 883 | that take advantage of being hosted on the user's domain name. The 884 | origin on which the remoteStorage server has its interface SHOULD 885 | therefore NOT be used for anything else, and the user SHOULD be 886 | warned not to visit any web pages on that origin. In particular, the 887 | OAuth dialog and launch dashboard or token revokation interface 888 | SHOULD be on a different origin than the remoteStorage interface. 889 | 890 | Where the use of bearer tokens is impractical, a user may choose to 891 | store documents on hard-to-guess URLs whose path after 892 | starts with '/public/', while sharing this URL only 893 | with the intended audience. That way, only parties who know the 894 | document's hard-to-guess URL, can access it. The server SHOULD 895 | therefore make an effort to detect and stop brute-force attacks that 896 | attempt to guess the location of such documents. 897 | 898 | The server SHOULD also detect and stop denial-of-service attacks 899 | that aim to overwhelm its interface with too much traffic. 900 | 901 | 902 | 903 | de Jong [Page 18] 904 | 905 | Internet-Draft remoteStorage June 2014 906 | 907 | 908 | 15. IANA Considerations 909 | 910 | This document registers the 'remotestorage' link relation, as well 911 | as the following WebFinger properties: 912 | * "http://remotestorage.io/spec/version" 913 | * "http://tools.ietf.org/html/rfc6749#section-4.2" 914 | * "http://tools.ietf.org/html/rfc6750#section-2.3" 915 | * "https://tools.ietf.org/html/rfc2616#section-14.16 916 | 917 | 16. Acknowledgements 918 | 919 | The authors would like to thank everybody who contributed to the 920 | development of this protocol, including Kenny Bentley, Javier Diaz, 921 | Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter 922 | Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph 923 | Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin 924 | Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco 925 | Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick 926 | Jennings, Markus Sabadello, Steven te Brinke, Matthias Treydte, 927 | Rick van Rein, Mark Nottingham, Julian Reschke, and Markus 928 | Lanthaler, among many others. 929 | 930 | 17. References 931 | 932 | 17.1. Normative References 933 | 934 | [WORDS] 935 | Bradner, S., "Key words for use in RFCs to Indicate Requirement 936 | Levels", BCP 14, RFC 2119, March 1997. 937 | 938 | [IRI] 939 | Duerst, M., "Internationalized Resource Identifiers (IRIs)", 940 | RFC 3987, January 2005. 941 | [WEBFINGER] 942 | Jones, P., Salguerio, G., Jones, M, and Smarr, J., 943 | "WebFinger", RFC7033, September 2013. 944 | 945 | [OAUTH] 946 | "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth 947 | 2.0 Authorization Framework", RFC6749, October 2012. 948 | 949 | 17.2. Informative References 950 | 951 | 952 | 953 | de Jong [Page 19] 954 | 955 | Internet-Draft remoteStorage June 2014 956 | 957 | 958 | [HTTPS] 959 | Rescorla, E., "HTTP Over TLS", RFC2818, May 2000. 960 | [HTTP] 961 | Fielding et al., "Hypertext Transfer Protocol -- HTTP/1.1", 962 | RFC2616, June 1999. 963 | 964 | [SPDY] 965 | Mark Belshe, Roberto Peon, "SPDY Protocol - Draft 3.1", http:// 966 | www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1, 967 | September 2013. 968 | 969 | [JSON-LD] 970 | M. Sporny, G. Kellogg, M. Lanthaler, "JSON-LD 1.0", W3C 971 | Proposed Recommendation, 972 | http://www.w3.org/TR/2014/REC-json-ld-20140116/, January 2014. 973 | 974 | [CORS] 975 | van Kesteren, Anne (ed), "Cross-Origin Resource Sharing -- 976 | W3C Candidate Recommendation 29 January 2013", 977 | http://www.w3.org/TR/cors/, January 2013. 978 | 979 | [MANIFEST] 980 | Mozilla Developer Network (ed), "App manifest -- Revision 981 | 330541", https://developer.mozilla.org/en- 982 | US/Apps/Build/Manifest$revision/566677, April 2014. 983 | 984 | [DATASTORE] 985 | "WebAPI/DataStore", MozillaWiki, retrieved May 2014. 986 | https://wiki.mozilla.org/WebAPI/DataStore#Manifest 987 | 988 | [KERBEROS] 989 | C. Neuman et al., "The Kerberos Network Authentication Service 990 | (V5)", RFC4120, 991 | https://tools.ietf.org/html/rfc4120 992 | 993 | [BEARER] 994 | M. Jones, D. Hardt, "The OAuth 2.0 Authorization Framework: 995 | Bearer Token Usage", RFC6750, 996 | http://tools.ietf.org/html/rfc6750#section-2.3, October 2012. 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | de Jong [Page 20] 1004 | 1005 | Internet-Draft remoteStorage June 2014 1006 | 1007 | 1008 | 18. Authors' addresses 1009 | 1010 | Michiel B. de Jong 1011 | (independent) 1012 | 1013 | Email: michiel@michielbdejong.com 1014 | 1015 | 1016 | F. Kooman 1017 | (independent) 1018 | 1019 | Email: fkooman@tuxed.net 1020 | 1021 | -------------------------------------------------------------------------------- /release/remotestorage-2010.12.html: -------------------------------------------------------------------------------- 1 | see https://raw.github.com/unhosted/website/cbb0b4ffbbbdf9ca3aa44e1e36e049235772660f/protocol.html 2 |

UJ/0.1

3 | 4 | UJ/0.1, or UJ/0.1-over-PubSign, is a protocol in which an unhosted web app that is running in a browser, makes an AJAX call to a storage node, to which the storage nodes responds. The call is made to the root of the domain, with the HTTP POST method. 5 |



6 |

PubSign-compatible command POST content:

7 | Although at some point we will probably change to the MagicEnvelope that Salmon uses, version 0.1 uses "PubSign" to sign things you publish. There are four commands, two of which follow the PubSign convention, which is as follows: 8 |
    9 |
  • "protocol": <name of the specific PubSign-compatible protocol> 10 |
  • "cmd": <whatever it is you want to publish - should adhere to protocol chosen above> 11 |
  • "PubSign": <raw hex RSA-signature signing the content of "cmd" above> 12 |
  • <other field/value pairs allowed by specific protocol...> 13 |
14 |
15 | The content of the POST must be exactly one of the following four possible commands (valid responses are below each one): 16 |



17 |

SET command POST content:

18 |
    19 |
  • "protocol": "UJ/0.1" 20 |
  • "cmd": <a JSON string, which decodes to:> 21 |
      22 |
    • "method": "SET" 23 |
    • "chan": <the channel, so for 'test@example.com', chan='test'> 24 |
    • "keyPath": <an ascii name for the key in the key-value store> 25 |
    • "value": <the value you want to store, can be any JSON object> 26 |
    27 |
  • "WriteCaps": <password for writing/receiving this chan on this storage node> 28 |
  • "PubSign": <raw hex RSA-signature signing the content of "cmd" above> 29 |
30 | Response: "OK" or "ERROR:" followed by some error message in English 31 |



32 |

GET command POST content:

33 |
    34 |
  • "protocol": "UJ/0.1" 35 |
  • "cmd": <a JSON string, which decodes to:> 36 |
      37 |
    • "method": "GET" 38 |
    • "chan": <the channel, so for 'test@example.com', chan='test'> 39 |
    • "keyPath": <an ascii name for the key in the key-value store> 40 |
    41 |
42 | Response: the JSON that was in the "value" field of the last SET command for that chan and keyPath, or "ERROR:" followed by some error message in English 43 |



44 |

SEND command POST content:

45 |
    46 |
  • "protocol": "UJ/0.1" 47 |
  • "cmd": <a JSON string, which decodes to:> 48 |
      49 |
    • "method": "SEND" 50 |
    • "chan": <the channel, so for 'test@example.com', chan='test'> 51 |
    • "keyPath": <an ascii name for a mailfolder within the chan's message store> 52 |
    • "value": <the body of the message, encrypted to the chan's public key> 53 |
    54 |
  • "PubSign": <raw hex RSA-signature signing the content of "cmd" above> 55 |
56 | Response: "OK" or "ERROR:" followed by some error message in English 57 |



58 |

RECEIVE command POST content:

59 |
    60 |
  • "protocol": "UJ/0.1" 61 |
  • "cmd": <a JSON string, which decodes to:> 62 |
      63 |
    • "method": "RECEIVE" 64 |
    • "chan": <the channel, so for 'test@example.com', chan='test'> 65 |
    • "keyPath": <an ascii name for the key in the key-value store> 66 |
    • "delete": <boolean indicating whether to delete or leave on server> 67 |
    68 |
  • "WriteCaps": <password for writing/receiving this chan on this storage node> 69 |
70 | Response: an array of messages, or an empty array, or "ERROR:" followed by some error message in English 71 |



72 |

Additionally, the storage node should:

73 |
    74 |
  • respond with correct CORS headers, in response to both HTTP POST and HTTP OPTIONS. 75 |
  • check the HTTP referer that the browser sent, and make sure it keeps all data of one referer completely separate from the data of another. 76 |
77 | If any of this is unclear or ambiguous, then check the reference implementation at: 78 | https://github.com/michiel-unhosted/unhosted/tree/v0.1 79 |
80 | This protocol is part of the alpha-release of the Unhosted project. It is likely to be changed to the next version before Unhosted reaches beta. For instance, the storage node could easily take advantage of the presence of PubSign signatures, and then we could get rid of the WriteCaps. Also, it has been suggested we should do at least the GET command with an HTTP GET method and not an HTTP POST. 81 |

82 | -------------------------------------------------------------------------------- /release/remotestorage-2011.04.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

The Unhosted WebDAV Standard

6 | v0.1.10 7 | 16 | 17 |

Introduction

18 |

An Unhosted WebDAV service acts as an always-on data storage 19 | for its users. It does not host any application source code, nor does it execute 20 | any processing. 21 | To make use of an Unhosted WebDAV account, the user visits unhosted web apps 22 | that run in a browser, or user-agent. This user-agent discovers the user's 23 | Unhosted WebDAV service through WebFinger. It then redirects the user to an OAuth 24 | dialogue, to establish authentication. Finally, it uses WebDAV to read and write the 25 | data on the Unhosted WebDAV account. 26 |

27 |

This document is meant for implementers of an 28 | Unhosted WebDAV service. It descibes how an Unhosted WebDAV 29 | service should behave. Developers of unhosted web apps should 30 | download the SDK instead. 31 | End-users who are interested in understanding the concepts 32 | behind the unhosted web, are better off reading this interview. In any case, follow our mailing list for all the latest updates.

In order 35 | for your service to comply with the Unhosted WebDAV standard, it needs to 36 | implement WebFinger, OAuth2-cs, WebDAV, Http Basic auth, and 37 | CORS. All of these technologies should be configured to work together 38 | in the way that this specification describes using the keywords "MUST", "SHOULD" 39 | and "SHOULD EITHER .. OR", in the meaning specified by rcf2119.

Throughout this 41 | text, we use several variables, which are written in capitals, and with a $-sign 42 | in front of them. They describe the interaction between a user, this user's 43 | Unhosted WebDAV service (aka her unhosted account), and an application. The 44 | variables are:

  • $USERNAME - the local part of the 45 | user's global user identifier, that's to say, the part before the '@' sign.
  • 46 |
  • $USERDOMAIN - the server part of the user's global user 47 | identifier, that's to say, the part after the '@' sign.
  • 48 |
  • $DAVURL - the base URL of the Unhosted WebDAV service.
  • 49 |
  • $APP - the URL of the application
  • 50 |
  • $RESOURCE - the string identifying the resource to which 51 | the user is granting the application access. By default, $RESOURCE = $APP.
  • 52 |
53 | 54 |

WebFinger

55 | 56 |

The Unhosted WebDAV service MUST 57 | implement WebFinger 59 | and with XRD 61 | formatting. In the WebFinger data for $USERNAME @ $USERDOMAIN, 62 | it should announce an https-based $DAVURL with 63 | rel-attribute 64 | 'http://unhosted.org/spec/dav/0.1' . All WebFinger-related 65 | resources SHOULD be served over 66 | https and MUST be served with CORS headers that allow their retrieval 68 | from any origin.

69 |

OAuth2-cs

70 |

Given the $DAVURL 71 | announced via WebFinger, the Unhosted WebDAV implementer MUST make OAuth2-cs 73 | available on $DAVURL/oauth2/auth (no need for CORS headers 74 | here, because OAuth does redirection and not AJAX).

75 |

WebDAV

76 |

Whenever a user $USERNAME @ $USERDOMAIN grants an 77 | $APP access to a $RESOURCE, the Unhosted 78 | WebDAV implementer MUST make a WebDAV server available on 79 | $DAVURL/webdav/$USERDOMAIN/$USERNAME/$RESOURCE/, with CORS 80 | headers that allow access from whichever $APP sends it valid 81 | credentials.

82 | 83 |

Non-normative hint: For this, it is necessary to 84 | exclude (apart from the HEAD and GET verbs) the OPTIONS verb 85 | from the http basic auth restrictions. It is also necessary to set the 86 | -Allow-Credentials header to true, and to echo the Origin specified in the 87 | request headers back in the -Allow-Origin header (setting it to '*' works for 88 | the Webfinger URL, but not here, because of an extra restriction on CORS that 89 | applies when there are credentials involved). An example of how to configure 90 | this in apache's mod_dav can be found here.

92 |

Http basic auth

93 |

Whenever a user, who is successfully identified as $USERNAME 94 | @ $USERDOMAIN by the OAuth2-cs authentication server, and has 95 | successfully granted application $APP access to resource 96 | $RESOURCE, the OAuth access token given out to the 97 | $APP MUST be a valid http basic auth password for 99 | $DAVURL/webdav/$USERDOMAIN/$USERNAME/$RESOURCE/, when combined 100 | with the concatenation of $USERNAME, '@', and 101 | $USERDOMAIN as the http basic auth username. 102 | Http basic auth MUST be required for all verbs except OPTIONS, HEAD and GET. 103 | This means all content on the DAV server will be read/write-able for $USERNAME 104 | @ $USERDOMAIN, and world-readable. It is the responsibility of the application to add 105 | a payload encryption layer on top of this (end-to-end encryption). 106 |

107 | 108 |

Registration

109 |

In addition to the 'lrdd' 111 | entry that is needed in 112 | https://$USERDOMAIN/.well-known/host-meta as part of WebFinger, 113 | the implementer of Unhosted WebDAV SHOULD also add a 'register' 114 | entry. It should contain a template with two variables: {uri} 115 | and {redirect_url}. It SHOULD then also offer a human-readable 116 | web page with registration information on the URL that is constructed by filling 117 | in the concatenation of 'acct:', $USERNAME, 118 | '@', and $USERDOMAIN for the 119 | {uri} variable, and a redirect URL for the 120 | {redirect_url} variable. This registration page SHOULD EITHER 121 | allow the user to register a new unhosted account identified by 122 | $USERNAME @ $USERDOMAIN, OR provide human-readable information 123 | about why this is not being offered (for instance, if the username is taken, or 124 | if the server is not currently accepting registrations, or if some out-of-band 125 | actions are needed, like "if you are an ACME employee, but haven't received the 126 | credentials for your unhosted account yet, then please contact the helpdesk on 127 | it@acme.com"). Whether registration as a new user is successful or not, upon 128 | completion the user-agent SHOULD be redirected back to the url that was filled 129 | in as the {redirect-url} in the template.

130 | 131 |

Conclusion

132 |

A non-normative example 133 | implementation of this specification is currently visible by visiting the 134 | world's first unhosted web app, on myfavouritesandwich.org, or 136 | viewing its javascript, 137 | and here is a stripped down 138 | representation of the http trace it produces. It is up to the application, and 139 | beyond the scope of this spec, to implement end-to-end encryption on top of 140 | this, possibly using the Stanford Javascript Crypto Library, possibly in a 141 | WebWorker process. This text may be changed for clarity - its history is tracked 142 | here. 144 | However, the standard it describes was frozen on 5 April 2011. 145 | The standard will not change for at least 6 months (until at least 5 146 | October 2011). Even then, if at any point after that improvements to 147 | this standard are deemed desirable, every attempt will be made to make those 148 | changes non-breaking. We will follow semantic versioning. 149 |

150 |
151 | -------------------------------------------------------------------------------- /release/remotestorage-2011.10.wiki: -------------------------------------------------------------------------------- 1 | see http://www.w3.org/community/unhosted/wiki/index.php?title=RemoteStorage-2011.10 2 | [[File:RemoteStorage.png]] 3 | = remoteStorage specification = 4 | 5 | == Introduction == 6 | 7 | Adding WebFinger, OAuth and Cross-Origin Resource Sharing (CORS) to an online storage makes it usable as per-user storage for web apps. This specification describes a common interface for such a per-user online data storage. 8 | 9 | To make use of a remoteStorage-compatible online storage account, the user visits an HTML application that runs in her browser, or user-agent: 10 | 11 | 1. Cross-Origin WebFinger lets the user-agent discover the user's remoteStorage. 12 | 2. The user is redirected to an OAuth dialogue, to establish authorization. 13 | 3. The web app uses AJAX with CORS to access the data on the user's remoteStorage account. 14 | 15 | This spec is for engineers who provide online storage to users of the web. See [http://unhosted.org/ unhosted.org] for more generic resources. 16 | 17 | Keyword "MUST" used as in rcf2119. We use the word "web address" to mean "URL". Some variables we will use: 18 | 19 | $TEMPLATE - a template for the web address (URL) of the storage, containing the string '{category}' (see [[#Storage|the section on Storage]]), 20 | $API - which exact HTTP API is exposed (see [[#API|the section on APIs]]), 21 | $AUTH - the OAuth end-point for obtaining $TOKEN (see [[#OAuth|the section on OAuth]]), 22 | $CATEGORY - a string that corresponds to one independent key-value store, e.g. 'contacts'. 23 | The 'public' category should be world-readable, but all others fully private. 24 | $TOKEN - the bearer token given out by the OAuth dialog. 25 | 26 | 27 | == WebFinger == 28 | 29 | The remoteStorage provider MUST provide [https://tools.ietf.org/html/draft-jones-appsawg-webfinger-01 WebFinger], and include a link of the following format (all $VARIABLES as described above): 30 | 31 | 32 | 33 | As discussed in the new WebFinger spec, all webfinger resources MUST be served with CORS headers that allow their retrieval from any origin. 34 | 35 | Example values in the WebFinger record for JohnDoe123@yourremotestorage.com could be: 36 | 37 | $TEMPLATE: "http://storage.yourremotestorage.com/JohnDoe123/{category}/" 38 | $API: "CouchDB" 39 | $AUTH: "http://auth.yourremotestorage.com/JohnDoe123/" 40 | 41 | == OAuth == 42 | 43 | The remoteStorage provider MUST make [http://tools.ietf.org/html/draft-ietf-oauth-v2-13#section-4.2 OAuth2's implicit grant flow] available on $AUTH. From now on, the resource scope the client requests will be called $CATEGORY, and the bearer token given in response will be called $TOKEN. 44 | The user should be informed that they are giving the app in question read/write access to $CATEGORY. If the user chooses to allow this, then $TOKEN should subsequently be accepted as sufficient credentials reading and writing to key-value store $CATEGORY (but see below about the special 'public' category). 45 | 46 | NOTE: since we got this wrong, all our client implementations wrongly send comma-separated scope lists instead of space-separated as OAuth prescribes, and since they were developed in tandem, all our server implementations accept that format. This will be corrected in the next version of this spec, but we did not find it necessary to update all deployed clients and servers with this correction. We therefore advise to (also) accept comma-separated scope lists when implementing a server, and to knowingly send comma-separated scope lists when talking to a server that announces it adheres to this version of the spec. 47 | 48 | == Storage == 49 | 50 | Whenever a user grants an app access to a $CATEGORY, the remoteStorage provider MUST give out a bearer token and make the key-value store corresponding to $CATEGORY available on the web address obtained by taking the template $TEMPLATE and replacing the string '{category}' with the actual $CATEGORY, implementing the API specified by $API, with [http://www.w3.org/TR/cors/ CORS headers] that allow any origin. 51 | 52 | Continuing the earlier example, say JohnDoe123@yourremotestorage.com allows a social app access to his 'contacts' category. Then (resolving $TEMPLATE "http://storage.yourremotestorage.com/JohnDoe123/{category}/" with $CATEGORY 'contacts') the web address for the his 'contacts' key-value store will be http://storage.yourremotestorage.com/JohnDoe123/contacts/ 53 | 54 | 55 | == API == 56 | 57 | The following are the valid values for $API. Please be aware that the second part of this list is entirely experimental and is very likely to change in future versions of this specification: 58 | 59 | Stable: 60 | 61 | [http://www.webdav.org/specs/rfc4918.html 'WebDAV'] 62 | 'simple' (recommended): WebDAV, but restricted to the GET, PUT, and DELETE verbs. 63 | [http://wiki.apache.org/couchdb/Complete_HTTP_API_Reference 'CouchDB'] 64 | 65 | Experimental: 66 | 67 | 'git' ([http://schacon.github.com/git/git-http-backend.html git smart http] is an implementation) 68 | [http://tahoe-lafs.org/trac/tahoe-lafs/browser/docs/frontends/webapi.rst 'tahoe-lafs'] 69 | [http://camlistore.org/code/?p=camlistore.git;f=doc/protocol;hb=master 'camlistore'] 70 | [http://docs.amazonwebservices.com/AmazonS3/latest/API/ 'AmazonS3'] 71 | [http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#UploadingDocs 'GoogleDocs'] 72 | [http://www.dropbox.com/developers/reference/api 'Dropbox'] 73 | 74 | Note that implementing these APIs as described in their respective implementations is not enough. You need to add CORS headers to them, accept OAuth bearer tokens as credentials, and announce the remoteStorage through WebFinger. If you aim to run a proxy for a proprietary service, then keep in mind that these proxies often need to register as API-using apps. 75 | 76 | 77 | == Special 'public' category == 78 | 79 | While for all access to any of the other categories, the corresponding $TOKEN is required, storage providers should implement the 'public' category slightly different. Read access, but NOT enumerate-access, should be allowed without $TOKEN. Here, read-access means access to retrieve a value /given/ its exact key. So a client should only have access to the values if it already knows the (possibly secret) keys. As an example, PROPFIND on WebDAV collections should not be allowed without credentials. Also, access to '/{category}/_changes' or to '/_all_dbs' on a CouchDB instance should only be offered if $TOKEN is presented in an HTTP header. 80 | 81 | 82 | == Conclusion == 83 | 84 | It is up to the application, and beyond the scope of this specification, to implement end-to-end encryption on top of this, possibly using the Stanford Javascript Crypto Library, possibly in a WebWorker process. This text may be changed for clarity - its history is tracked by this wiki. However, the standard it describes was frozen on 31 October 2011. The standard will not change for at least 6 months (until at least 31 April 2012). Even then, if at any point after that improvements to this standard are deemed desirable, every attempt will be made to make those changes non-breaking. 85 | -------------------------------------------------------------------------------- /release/remotestorage-2012.04.wiki: -------------------------------------------------------------------------------- 1 | see http://www.w3.org/community/unhosted/wiki/index.php?title=RemoteStorage-2012.04 2 | [[File:RemoteStorage.png]] 3 | = remoteStorage specification = 4 | 5 | == Introduction == 6 | 7 | Adding WebFinger, OAuth and Cross-Origin Resource Sharing (CORS) to an online storage makes it usable as per-user storage for web apps. This specification describes a common interface for such a per-user online data storage. 8 | 9 | To make use of a remoteStorage-compatible online storage account, the user visits an HTML application that runs in her browser, or user-agent: 10 | 11 | 1. Cross-Origin WebFinger lets the user-agent discover the user's remoteStorage. 12 | 2. The user is redirected to an OAuth dialogue, to establish authorization. 13 | 3. The web app uses AJAX with CORS to access the data on the user's remoteStorage account. 14 | 15 | This spec is for engineers who provide online storage to users of the web. See [http://unhosted.org/ unhosted.org] for more generic resources. 16 | 17 | Keyword "MUST" used as in rcf2119. We use the word "web address" to mean "URL". 18 | 19 | == WebFinger == 20 | 21 | The remoteStorage provider MUST provide [http://tools.ietf.org/html/draft-ietf-appsawg-webfinger-00 WebFinger] - at least the JRD format on host-meta, over https, as follows: 22 | 23 | GET /.well-known/host-meta?resource=acct:bob@example.com 24 | 25 | HTTP/1.1 200 OK 26 | access-control-allow-origin: * 27 | content-type: application/json 28 | 29 | { 30 | links:[{ 31 | href: 'https://example.com/storage/bob', 32 | rel: "remoteStorage", 33 | type: "https://www.w3.org/community/rww/wiki/read-write-web-00#simple", 34 | properties: { 35 | 'auth-method': "https://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2", 36 | 'auth-endpoint': 'https://example.com/auth/bob 37 | } 38 | }] 39 | } 40 | 41 | Here, the following are example values, the rest is literal: 42 | 43 | bob@example.com - user@host 44 | https://example.com/storage/bob - root of the storage for bob@example.com, {storageRoot} in the rest of this text 45 | https://example.com/auth/bob - OAuth authorization end-point 46 | 47 | == OAuth == 48 | 49 | The remoteStorage provider MUST make [http://tools.ietf.org/html/draft-ietf-oauth-v2-13#section-4.2 OAuth2's implicit grant flow] available on the announced OAuth end-point. The storage is understood as a directory tree, with files and directories contained in other directories, where directories are represented by paths that end in a forward slash. Any resource that does not end in forward slash is said to be a file. A resource is contained in any directory whose path forms a prefix of its path, and directly contained in the containing directory with the longest path. For instance, 50 | 51 | {storageRoot}/path/to/file 52 | 53 | is contained in all of the following directories: 54 | 55 | {storageRoot}/ 56 | {storageRoot}/path/ 57 | {storageRoot}/path/to/ 58 | 59 | But only directly contained in {storageRoot}/path/to/. Likewise, {storageRoot}/path/to/ is directly contained in {storageRoot}/path/. A certain type of access (read or read-write) to a directory implies access to all resources contained in it. Valid scopes are of the format 60 | 61 | directory:r 62 | 63 | for read-only access to both {storageRoot}/directory/ and {storageRoot}/public/directory/, or 64 | 65 | directory:rw 66 | 67 | for read-write access to both {storageRoot}/directory/ and {storageRoot}/public/directory/, where the string 'directory' 68 | is an example. For read and read-write 69 | access to the entire storage (every resource contained in {storageRoot}/) you would need the following special scopes, respectively: 70 | 71 | :r 72 | :rw 73 | 74 | == Storage == 75 | 76 | Whenever a user grants an app read or read-write access to a certain directory path, the remoteStorage provider MUST give out a bearer token that gives access to the directory path on the web address obtained by taking the storage root that was announced and appending a forward slash followed by the directory path in question, implementing the GET, PUT and DELETE verbs, over https and with [http://www.w3.org/TR/cors/ CORS headers] that allow any origin (echoing back the Origin for PUT, DELETE and OPTIONS). 77 | 78 | == GET == 79 | 80 | In response to a GET request whose path does not end in a forward slash, and with a valid read or read-write token for the resource or one of its parent directories, the content from the last PUT, with the Content-Type header from the last PUT and a Last-Modified header indicating the time of the last PUT. 81 | 82 | If there never was a PUT to this resource, or there was a DELETE to the resource or one of its parent directories since the last PUT, then a 404 status should be returned. 83 | 84 | If the path ends in a forward slash, then the direct contents of the directory should be displayed in a filename -> timestamp map, displaying direct sub-directories with a trailing slash on the file name. For instance if the storage currently contains: 85 | 86 | foo/bar/baz/boo: { 87 | content: 'some content', 88 | contentType: 'text/plain', 89 | timestamp: 1234588888 90 | }, 91 | foo/bla: { 92 | content: '{"more": "content"}', 93 | contentType: 'application/json', 94 | timestamp: 1234544444 95 | } 96 | 97 | then a GET to {storageRoot}/foo/ should return: 98 | Access-Control-Allow-Origin: * 99 | Content-Type: application/json 100 | Last-Modified: Sat Feb 14 2009 06:21:28 GMT+0100 (CET) 101 | 102 | { 103 | "bla": 1234544444, 104 | "bar/": 12345888888 105 | } 106 | 107 | So for a directory it will return the highest timestamp of any item in the whole subtree under that directory. A GET to an empty/absent directory should return a 200 status with a "{}" in the body, rather than a 404 status. 108 | 109 | == PUT == 110 | 111 | A PUT with a valid read-write token for the resource should result in storing the data sent in the body, the timestamp when it happens, and the content-type header sent. It should respond with CORS headers and a Last-Modified header indicating the timestamp that was recorded. No two PUTs should be accepted during the same clock second, so that the timestamp can be used to uniquely refer to document versions. PUT requests to paths that end in a forward slash have no effect, because each directory "exists" automatically when a resource is put inside it. 112 | 113 | == DELETE == 114 | 115 | A DELETE with a valid read-write token for the resource should delete the resource from the storage. DELETEs whose path end in a trailing slash are meaningless and have no effect. Once the last resource from a directory has been deleted, the directory is empty which is functionally equivalent to that directory not existing. 116 | 117 | == OPTIONS == 118 | 119 | Don't forget to implement the OPTIONS verb as prescribed by CORS. 120 | 121 | == Special 'public' directory == 122 | 123 | While for all access to any of the other resources, the corresponding access token is required, storage providers should serve resources that fall under the 'public/' directory, and are not themselves directories, even if the Authorization header is missing or incorrect. So for GET of a resource whose path starts with {storageRoot}/public/ and does not end in a forward slash, the server should never give a 401 response status. 124 | 125 | == Response codes == 126 | If a bearer token is presented that would have given access to the requested action, but is unknown, expired or has been revoked, or if the bearer token (if present) does not give access to the document, then the response status should be a 401 or a 403, respectively. Otherwise, if the resource does not exist and is not a directory, the response status for retrieval and deletion should be a 404. Otherwise the response status should be one of 200 for OK, 500 for Internal Server Errors, and 400 for all errors which the server categorizes as caused by the client's behaviour. The server may also close the connection without responding, or not respond and leave it to the client to close the connection. 127 | 128 | == Conclusion == 129 | 130 | This text may be changed for clarity - its history is tracked by this wiki. However, the standard it describes was frozen on 6 August 2012 (a bit delayed from the 30 April originally planned). The standard will not change until at least 31 October 2012. Even then, if at any point after that improvements to this standard are deemed desirable, every attempt will be made to make those changes non-breaking. 131 | 132 | -------------------------------------------------------------------------------- /source.txt: -------------------------------------------------------------------------------- 1 | INTERNET DRAFT Michiel B. de Jong 2 | Document: draft-dejong-remotestorage-${VERSION} (independent) 3 | F. Kooman 4 | (independent) 5 | S. Kippe 6 | Intended Status: Proposed Standard (independent) 7 | Expires: ${EXPIRES} ${DATE} 8 | 9 | 10 | remoteStorage 11 | 12 | Abstract 13 | 14 | This draft describes a protocol by which client-side applications, 15 | running inside a web browser, can communicate with a data storage 16 | server that is hosted on a different domain name. This way, the 17 | provider of a web application need not also play the role of data 18 | storage provider. The protocol supports storing, retrieving, and 19 | removing individual documents, as well as listing the contents of an 20 | individual folder, and access control is based on bearer tokens. 21 | 22 | Status of this Memo 23 | 24 | This Internet-Draft is submitted in full conformance with the 25 | provisions of BCP 78 and BCP 79. 26 | 27 | Internet-Drafts are working documents of the Internet Engineering 28 | Task Force (IETF). Note that other groups may also distribute 29 | working documents as Internet-Drafts. The list of current Internet- 30 | Drafts is at http://datatracker.ietf.org/drafts/current/. 31 | 32 | Internet-Drafts are draft documents valid for a maximum of six months 33 | and may be updated, replaced, or obsoleted by other documents at any 34 | time. It is inappropriate to use Internet-Drafts as reference 35 | material or to cite them other than as "work in progress." 36 | 37 | This Internet-Draft will expire on ${EXPIRES}. 38 | 39 | Copyright Notice 40 | 41 | Copyright (c) ${YEAR} IETF Trust and the persons identified as the 42 | document authors. All rights reserved. 43 | 44 | This document is subject to BCP 78 and the IETF Trust's Legal 45 | Provisions Relating to IETF Documents 46 | (http://trustee.ietf.org/license-info) in effect on the date of 47 | publication of this document. Please review these documents 48 | carefully, as they describe your rights and restrictions with respect 49 | to this document. Code Components extracted from this document must 50 | include Simplified BSD License text as described in Section 4.e of 51 | the Trust Legal Provisions and are provided without warranty as 52 | described in the Simplified BSD License. 53 | Table of Contents 54 | 55 | 1. Introduction...................................................2 56 | 2. Terminology....................................................3 57 | 3. Storage model..................................................3 58 | 4. Requests.......................................................4 59 | 5. Response codes.................................................7 60 | 6. Versioning.....................................................8 61 | 7. CORS headers...................................................8 62 | 8. Session description............................................9 63 | 9. Bearer tokens and access control...............................9 64 | 10. Application-first bearer token issuance.......................10 65 | 11. Storage-first bearer token issuance...........................12 66 | 12. Example wire transcripts......................................12 67 | 12.1. WebFinger................................................12 68 | 12.2. OAuth dialog form........................................13 69 | 12.3. OAuth dialog form submission.............................14 70 | 12.4. OPTIONS preflight........................................14 71 | 12.5. Initial PUT..............................................15 72 | 12.6. Subsequent PUT...........................................15 73 | 12.7. GET......................................................16 74 | 12.8. DELETE...................................................17 75 | 13. Distributed versioning........................................18 76 | 14. Security Considerations.......................................19 77 | 15. IANA Considerations...........................................20 78 | 16. Acknowledgments...............................................20 79 | 17. References....................................................20 80 | 17.1. Normative References.....................................20 81 | 17.2. Informative References...................................21 82 | 18. Authors' addresses............................................22 83 | 84 | 85 | 1. Introduction 86 | 87 | Many services for data storage are available over the Internet. This 88 | specification describes a vendor-independent interface for such 89 | services. It is based on HTTPS, CORS and bearer tokens. The 90 | metaphor for addressing data on the storage is that of folders 91 | containing documents and subfolders. The actions the interface 92 | exposes are: 93 | 94 | * GET a folder: retrieve the names and current versions of the 95 | documents and subfolders currently contained by the folder 96 | * GET a document: retrieve its content type, current version, 97 | and contents 98 | 99 | * PUT a document: store a new version, its content type, and 100 | contents, conditional on the current version 101 | 102 | * DELETE a document: remove it from the storage, conditional on 103 | the current version 104 | 105 | * HEAD a folder or document: like GET, but omitting the response 106 | body 107 | 108 | The exact details of these five actions are described in this 109 | specification. 110 | 111 | 2. Terminology 112 | 113 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 114 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 115 | document are to be interpreted as described in RFC 2119 [WORDS]. 116 | 117 | "SHOULD" and "SHOULD NOT" are appropriate when valid exceptions to a 118 | general requirement are known to exist or appear to exist, and it is 119 | infeasible or impractical to enumerate all of them. However, they 120 | should not be interpreted as permitting implementors to fail to 121 | implement the general requirement when such failure would result in 122 | interoperability failure. 123 | 124 | 3. Storage model 125 | 126 | The server stores data in nodes that form a tree structure. 127 | Internal nodes are called 'folders' and leaf nodes are called 128 | 'documents'. For a folder, the server stores references to nodes 129 | contained in the folder, and it should be able to produce a list of 130 | them, with for each contained item: 131 | 132 | * item name 133 | * item type (folder or document) 134 | * current version (ETag) 135 | 136 | The list also contains, for each document in the list: 137 | 138 | * content type (media type, a.k.a. MIME type) 139 | * content length 140 | * last-modified date 141 | 142 | Apart from this folder and document metadata, the server should also 143 | be able to produce the current content of each document. 144 | 145 | 4. Requests 146 | 147 | Client-to-server requests SHOULD be made over HTTPS [HTTPS], and 148 | servers MUST comply with HTTP/1.1 [HTTP]. Specifically, they 149 | MUST support chunked transfer coding on PUT requests. Servers MAY 150 | also offer an optional switch to HTTP/2 [HTTP/2]. 151 | 152 | A request is considered successful if the HTTP response code is in 153 | the 2xx range (e.g. 200 OK, 201 Created), and unsuccessful if an 154 | error occurred or a condition was not met, e.g. response code 404 155 | Not Found, 304 Not Modified. 156 | 157 | The root folder of the storage tree is represented by the following 158 | URL: 159 | 160 | URI_ENCODE( '/' ) 161 | 162 | Subsequently, let be the URL of a folder, i.e. ends 163 | with a '/', then the URL of an item contained in it is: 164 | 165 | URI_ENCODE( ) 166 | 167 | for a document, or: 168 | 169 | URI_ENCODE( '/' ) 170 | 171 | for a folder. 172 | 173 | If a document with document_name exists, then no folder with 174 | folder_name can exist in the same parent folder, and vice versa. 175 | 176 | Item names MAY contain all characters, before URI_ENCODE, except '/' 177 | and the null character '\0' and MUST NOT have zero length. Item 178 | names MUST NOT be equal to '.' or to '..', as those have a special 179 | semantic in URIs (Section 5.2.4 of [URI]). 180 | 181 | A document description is a map containing one string-valued 'ETag' 182 | field, one string-valued 'Content-Type', one integer-valued 183 | 'Content-Length' field, and one string-valued 'Last-Modified' field. 184 | They represent the document's current version, its content type, its 185 | content length, and last-modfied date respectively. The 186 | last-modified date MUST be formatted as HTTP-date (Section 7.1.1.1 187 | of [HTTP]). Note that content length is measured in octets (bytes), 188 | not in characters. 189 | 190 | A folder description is a map containing a string-valued 'ETag' 191 | field, representing the folder's current version. 192 | 193 | A successful GET request to a folder MUST be responded to with a 194 | JSON-LD [JSON-LD] document (content type 'application/ld+json'), 195 | containing as its 'items' field a map in which contained documents 196 | appear as entries to a document description, and 197 | contained non-empty folders appear as entries '/' to a 198 | folder description. It MUST also contain an '@context' field with 199 | the value 'http://remotestorage.io/spec/folder-description'. For 200 | instance: 201 | 202 | { 203 | "@context": "http://remotestorage.io/spec/folder-description", 204 | "items": { 205 | "abc": { 206 | "ETag": "DEADBEEFDEADBEEFDEADBEEF", 207 | "Content-Type": "image/jpeg", 208 | "Content-Length": 82352, 209 | "Last-Modified": "Sat, 2 Jun 2018 15:58:23 GMT" 210 | }, 211 | "def/": { 212 | "ETag": "1337ABCD1337ABCD1337ABCD" 213 | } 214 | } 215 | } 216 | 217 | 218 | GET requests to empty folders SHOULD be responded to with a folder 219 | description with no items (the items field set to '{}'). However, an 220 | empty folder MUST NOT be listed as an item in its parent folder. 221 | 222 | PUT and DELETE requests only need to be made to documents, and never 223 | to folders. A document PUT will make all ancestor folders along its 224 | path become non-empty; deleting the last document from a subtree 225 | will make that whole subtree become empty. Folders will therefore 226 | show up in their parent folder descriptions if and only if their 227 | subtree contains at least one document. 228 | 229 | In contexts outside of this document, non-empty folders may be 230 | called 'existent', while empty folders may be called 'non-existent'. 231 | 232 | A successful GET request to a document SHOULD be responded to with 233 | the full document contents in the body, the document's content type 234 | in a 'Content-Type' header, its content length in octets (not in 235 | characters) in a 'Content-Length' header, and the document's current 236 | version as a strong ETag in an 'ETag' header. 237 | 238 | Note that the use of strong ETags prohibits changing the response 239 | body based on request headers; in particular, the server will not be 240 | able to serve the same document uncompressed to some clients and 241 | compressed to other clients when requested, since the two bodies 242 | would not be identical byte-for-byte. 243 | 244 | Servers MAY support Content-Range headers [RANGE] on GET requests, 245 | but whether or not they do SHOULD be announced both through the 246 | "http://tools.ietf.org/html/rfc7233" option mentioned below in 247 | section 10 and through the HTTP 'Accept-Ranges' response header. 248 | 249 | A successful PUT request to a document MUST result in: 250 | 251 | * the request body being stored as the document's new content, 252 | * parent and further ancestor folders being silently created as 253 | necessary, with the document (name and version) being added to 254 | its parent folder, and each folder added to its subsequent 255 | parent, 256 | * the value of its Content-Type header being stored as the 257 | document's new content type, 258 | * its version being updated, as well as that of its parent folder 259 | and further ancestor folders, using a strong validator [HTTP, 260 | section 7.2]. 261 | 262 | If no valid Content-Type header was received as part of a PUT 263 | request, the server MAY refuse to process the request, and instead 264 | respond with a descriptive error message in the body, as well as a 265 | http response code from the 4xx range. 266 | 267 | RemoteStorage does not place any restrictions on the value of 268 | Content-Type other than what is defined in [HTTP, section 3.1.1.5]. 269 | 270 | The response MUST contain a strong ETag header, with the document's 271 | new version (for instance a hash of its contents) as its value. 272 | 273 | A successful DELETE request to a document MUST result in: 274 | 275 | * the deletion of that document from the storage, and from its 276 | parent folder, 277 | * silent deletion of the parent folder if it is left empty by 278 | this, and so on for further ancestor folders, 279 | * the version of its parent folder being updated, as well as that 280 | of further ancestor folders. 281 | 282 | A successful HEAD request SHOULD be responded to like to the 283 | equivalent GET request, but omitting the response body. 284 | 285 | A successful OPTIONS request SHOULD be responded to as described in 286 | the CORS section below. 287 | 288 | 5. Response codes 289 | 290 | Response codes SHOULD be given as defined by [HTTP, section 6] and 291 | [BEARER, section 3.1]. The following is a non-normative list of 292 | status codes that are likely to occur in practice: 293 | 294 | * 2xx for all successful requests. 295 | * 304 for a conditional GET request whose precondition 296 | fails (see "Versioning" below), 297 | * 401 for all requests that require a valid bearer token and 298 | where no valid one was sent (see also [BEARER, section 299 | 3.1]), 300 | * 403 for all requests that have insufficient scope, e.g. 301 | accessing a for which no scope was obtained, or 302 | accessing data outside the user's , 303 | * 404 for all DELETE, GET and HEAD requests to documents that do 304 | not exist on the storage, 305 | * 409 for a PUT request where any folder name in the path 306 | clashes with an existing document's name at the same 307 | level, or where the document name coincides with an 308 | existing folder's name at the same level. 309 | * 412 for a conditional PUT or DELETE request whose precondition 310 | fails (see "Versioning" below), 311 | * 413 if the payload is too large, e.g. when the server has a 312 | maximum upload size for documents 313 | * 414 if the request URI is too long, 314 | * 416 if Range requests are supported by the server and the Range 315 | request can not be satisfied, 316 | * 429 if the client makes too frequent requests or is suspected 317 | of malicious activity, 318 | * 4xx for all malformed requests, e.g. reserved characters in the 319 | path [URI, section 2.2], as well as for all PUT and DELETE 320 | requests to folders, 321 | * 500 if an internal server error occurred, 322 | * 507 in case the account is over its storage quota, 323 | 324 | Clients SHOULD also handle the case where a response takes too long 325 | to arrive, or where no response is received at all. 326 | 327 | 6. Versioning 328 | 329 | All successful GET, HEAD, PUT and DELETE requests MUST return an 330 | 'ETag' header [HTTP] with, in the case of GET and HEAD the current 331 | version, in the case of PUT, the new version, and in case of DELETE, 332 | the version that was deleted. All successful GET requests MUST 333 | return a 'Cache-Control: no-cache' header. PUT and DELETE requests 334 | MAY have an 'If-Match' request header [COND], and MUST fail with a 335 | 412 response code if that does not match the document's current 336 | version. 337 | 338 | GET requests MAY have a comma-separated list of revisions in an 339 | 'If-None-Match' header [COND], and SHOULD be responded to with a 304 340 | response if that list includes the document or folder's current 341 | version. A PUT request MAY have an 'If-None-Match: *' header [COND], 342 | in which case it MUST fail with a 412 response code if the document 343 | already exists. 344 | 345 | A provider MAY offer version rollback functionality to its users, 346 | but this specification does not define the interface for that. 347 | 348 | 7. CORS headers 349 | 350 | All responses MUST carry CORS headers [CORS]. The server MUST also 351 | reply to preflight OPTIONS requests as per CORS. 352 | 353 | 354 | 355 | 8. Session description 356 | 357 | The information that a client needs to receive in order to be able 358 | to connect to a server SHOULD reach the client as described in the 359 | 'bearer token issuance' sections below. It consists of: 360 | 361 | * , consisting of 'https://' followed by a server 362 | host, and optionally a server port and a path prefix as per 363 | [IRI]. Examples: 364 | * 'https://example.com' (host only) 365 | * 'https://example.com:8080' (host and port) 366 | * 'https://example.com/path/to/storage' (host, port and 367 | path prefix; note there is no trailing slash) 368 | * as per [OAUTH]. The token SHOULD be hard to 369 | guess and SHOULD NOT be reused from one client to another. It 370 | can however be reused in subsequent interactions with the same 371 | client, as long as that client is still trusted. Example: 372 | 'ofb24f1ac3973e70j6vts19qr9v2eei' 373 | * , always 'draft-dejong-remotestorage-${VERSION}' for this 374 | alternative version of the specification. 375 | 376 | The client can make its requests using HTTPS with CORS and bearer 377 | tokens, to the URL that is the concatenation of with 378 | '/' plus one or more '/' strings indicating a path in the 379 | folder tree, followed by zero or one strings, indicating 380 | a document. For example, if is 381 | "https://storage.example.com/bob", then to retrieve the folder 382 | contents of the /public/documents/ folder, or to retrieve a 383 | 'draft.txt' document from that folder, the client would make 384 | requests to, respectively: 385 | 386 | * https://storage.example.com/bob/public/documents/ 387 | * https://storage.example.com/bob/public/documents/draft.txt 388 | 389 | 9. Bearer tokens and access control 390 | 391 | A bearer token represents one or more access scopes. These access 392 | scopes are represented as strings of the form , 393 | where the string SHOULD be lower-case alphanumerical, other 394 | than the reserved word 'public', and can be ':r' or ':rw'. 395 | The access the bearer token gives is the sum of its access scopes, 396 | with each access scope representing the following permissions: 397 | 398 | '*:rw') any request, 399 | 400 | '*:r') any GET or HEAD request, 401 | 402 | ':rw') any requests to paths relative to 403 | that start with '/' '/' or 404 | '/public/' '/', 405 | 406 | ':r') any GET or HEAD requests to paths relative to 407 | that start with 408 | '/' '/' or '/public/' '/', 409 | 410 | As a special exceptions, GET and HEAD requests to a document (but 411 | not a folder) whose path starts with '/public/' are always allowed. 412 | They, as well as OPTIONS requests, can be made without a bearer 413 | token. Unless [KERBEROS] is used (see section 10 below), all other 414 | requests SHOULD present a bearer token with sufficient access scope, 415 | using a header of the following form (no double quotes here): 416 | 417 | Authorization: Bearer 418 | 419 | In addition, providing the access token via a HTTP query parameter 420 | for GET requests MAY be supported by the server, although its use 421 | is not recommended, due to its security deficiencies; see [BEARER, 422 | section 2.3]. If supported, this SHOULD be announce through the 423 | "http://tools.ietf.org/html/rfc6750#section-2.3" WebFinger property 424 | as per section 10 below. 425 | 426 | 10. Application-first bearer token issuance 427 | 428 | To make a remoteStorage server available as 'the remoteStorage of 429 | the person identified by ', exactly one link of the following 430 | format SHOULD be added to the WebFinger record [WEBFINGER] for 431 | : 432 | 433 | { 434 | "href": , 435 | "rel": "http://tools.ietf.org/id/draft-dejong-remotestorage", 436 | "properties": { 437 | "http://remotestorage.io/spec/version": , 438 | "http://tools.ietf.org/html/rfc6749#section-4.2": , 439 | "...": "...", 440 | } 441 | } 442 | 443 | A common way of identifying persons as at is through a 444 | URI of the format "acct:@". Persons who use a personal 445 | domain name, not shared with any other users, can be identified by 446 | a URI of the format "http:///" (see [WEBFINGER, section 4.1]). 447 | 448 | Here and are as per "Session 449 | description" above, and SHOULD be either null or a 450 | URL where an OAuth 2.0 implicit-grant flow dialog [OAUTH] is 451 | presented. 452 | 453 | If is a URL, the user can supply their credentials 454 | for accessing the account (how, is out of scope), and allow or 455 | reject a request by the connecting application to obtain a bearer 456 | token for a certain list of access scopes. Note that an account 457 | will often belong to just one human user, but may also belong to a 458 | group of multiple users (the remoteStorage of at ). 459 | 460 | If is null, the client will not have a way to obtain 461 | an access token, and SHOULD send all requests without Authorization 462 | header, and rely on Kerberos [KERBEROS] instead for requests that 463 | would normally be sent with a bearer token, but servers SHOULD NOT 464 | impose any such access barriers for resources that would normally 465 | not require an access token. 466 | 467 | The '...' ellipses indicate that more properties may be present. 468 | Non-breaking examples that have been proposed so far, include a 469 | "http://tools.ietf.org/html/rfc6750#section-2.3" property, set to 470 | the string value "true" if the server supports passing the bearer 471 | token in the URI query parameter as per section 2.3 of [BEARER], 472 | instead of in the request header. 473 | 474 | Another example is "http://tools.ietf.org/html/rfc7233" with a 475 | string value of "GET" if Content-Range headers are supported for 476 | GET requests as per [RANGE]. 477 | 478 | Both these proposals are non-breaking extensions, since the client 479 | will have a way to work around it if these features are not present 480 | (e.g. retrieve the protected resource asynchronously in the first 481 | case, or request the entire resource in the second case). 482 | 483 | A "http://remotestorage.io/spec/web-authoring" property has been 484 | proposed with a string value of the fully qualified domain name to 485 | which web authoring content is published if the server supports web 486 | authoring as per [AUTHORING]. Note that this extension is a breaking 487 | extension in the sense that it divides users into "haves", whose 488 | remoteStorage accounts allow them to author web content, and 489 | "have-nots", whose remoteStorage account does not support this 490 | functionality. 491 | 492 | The server MAY expire bearer tokens, and MAY require the user to 493 | register applications as OAuth clients before first use; if no 494 | client registration is required, the server MUST ignore the value of 495 | the client_id parameter in favor of relying on the origin of the 496 | redirect_uri parameter for unique client identification. See section 497 | 4 of [ORIGIN] for computing the origin. 498 | 499 | 11. Storage-first bearer token issuance 500 | 501 | To request that the application connects to the user account 502 | ' ' , providers MAY redirect to applications with a 503 | 'remotestorage' field in the URL fragment, with the user account as 504 | value. 505 | 506 | The appplication MUST make sure this request is intended by the 507 | user. It SHOULD ask for confirmation from the user whether they want 508 | to connect to the given provider account. After confirmation, it 509 | SHOULD connect to the given provider account, as defined in Section 510 | 10. 511 | 512 | If the 'remotestorage' field exists in the URL fragment, the 513 | application SHOULD ignore any other parameters such as 514 | 'access_token' or 'state', to ensure compatibility with servers 515 | that implement older versions of this specification. 516 | 517 | 12. Example wire transcripts 518 | 519 | The following examples are not normative ("\" indicates a line was 520 | wrapped). 521 | 522 | 12.1. WebFinger 523 | 524 | In application-first, an in-browser application might issue the 525 | following request, using XMLHttpRequest and CORS: 526 | 527 | GET /.well-known/webfinger?resource=acct:michiel@michielbdejon\ 528 | g.com HTTP/1.1 529 | Host: michielbdejong.com 530 | 531 | and the server's response might look like this: 532 | 533 | HTTP/1.1 200 OK 534 | Access-Control-Allow-Origin: * 535 | Content-Type: application/jrd+json 536 | 537 | { 538 | "links":[{ 539 | "href": "https://michielbdejong.com:7678/inbox", 540 | "rel": "post-me-anything" 541 | }, { 542 | "href": "https://michielbdejong.com/me.jpg", 543 | "rel": "avatar" 544 | }, { 545 | "href": "https://3pp.io:4439/storage/michiel", 546 | "rel": "http://tools.ietf.org/id/draft-dejong-remotestorag\ 547 | e", 548 | "properties": { 549 | "http://remotestorage.io/spec/version": "draft-dejong-re\ 550 | motestorage-${VERSION}", 551 | "http://tools.ietf.org/html/rfc6749#section-4.2": "https\ 552 | ://3pp.io:4439/oauth/michiel", 553 | "http://tools.ietf.org/html/rfc6750#section-2.3": null, 554 | "http://tools.ietf.org/html/rfc7233": null, 555 | "http://remotestorage.io/spec/web-authoring": null 556 | } 557 | }] 558 | } 559 | 560 | 12.2. OAuth dialog form 561 | 562 | Once the in-browser application has discovered the server's OAuth 563 | end-point, it will typically redirect the user to this URL, in 564 | order to obtain a bearer token. Say the application is hosted on 565 | https://drinks-unhosted.5apps.com/ and wants read-write access to 566 | the account's "myfavoritedrinks" scope: 567 | 568 | GET /oauth/michiel?redirect_uri=https%3A%2F%2Fdrinks-unhosted.5\ 569 | apps.com%2F&scope=myfavoritedrinks%3Arw&client_id=https%3A%2F%2Fdrinks-\ 570 | unhosted.5apps.com&response_type=token HTTP/1.1 571 | Host: 3pp.io 572 | 573 | The server's response might look like this (truncated for brevity): 574 | 575 | HTTP/1.1 200 OK 576 | 577 | 578 | 579 | 580 | Allow access? 581 | ... 582 | 583 | 12.3. OAuth dialog form submission 584 | 585 | When the user submits the form, the request would look something 586 | like this: 587 | 588 | POST /oauth HTTP/1.1 589 | Host: 3pp.io:4439 590 | Origin: https://3pp.io:4439 591 | Content-Type: application/x-www-form-urlencoded 592 | Referer: https://3pp.io:4439/oauth/michiel?redirect_uri=https%3\ 593 | A%2F%2Fdrinks-unhosted.5apps.com%2F&scope=myfavoritedrinks%3Arw&client_\ 594 | id=https%3A%2F%2Fdrinks-unhosted.5apps.com&response_type=token 595 | 596 | client_id=https%3A%2F%2Fdrinks-unhosted.5apps.com&redirect_uri=\ 597 | https%3A%2F%2Fdrinks-unhosted.5apps.com%2F&response_type=token&scope=my\ 598 | favoritedrinks%3Arw&username=michiel&password=something&allow=Al\ 599 | low 600 | 601 | To which the server could respond with a 302 redirect, back to the 602 | origin of the requesting application: 603 | 604 | HTTP/1.1 302 Found 605 | Location: https://drinks-unhosted.5apps.com/#access_token=j2YnG\ 606 | tXjzzzHNjkd1CJxoQubA1o%3D&token_type=bearer 607 | 608 | 12.4. OPTIONS preflight 609 | 610 | When an in-browser application makes a cross-origin request which 611 | may affect the server-state, the browser will make a preflight 612 | request first, with the OPTIONS verb, for instance: 613 | 614 | OPTIONS /storage/michiel/myfavoritedrinks/ HTTP/1.1 615 | Host: 3pp.io:4439 616 | Access-Control-Request-Method: GET 617 | Origin: https://drinks-unhosted.5apps.com 618 | Access-Control-Request-Headers: Authorization 619 | Referer: https://drinks-unhosted.5apps.com/ 620 | 621 | To which the server can for instance respond: 622 | 623 | HTTP/1.1 200 OK 624 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 625 | Access-Control-Allow-Methods: GET, PUT, DELETE 626 | Access-Control-Allow-Headers: Authorization, Content-Length, Co\ 627 | ntent-Type, Origin, X-Requested-With, If-Match, If-None-Match 628 | 629 | 12.5. Initial PUT 630 | 631 | An initial PUT may contain an 'If-None-Match: *' header, like this: 632 | 633 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 634 | Host: 3pp.io:4439 635 | Content-Length: 91 636 | Origin: https://drinks-unhosted.5apps.com 637 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 638 | Content-Type: application/json; charset=UTF-8 639 | Referer: https://drinks-unhosted.5apps.com/? 640 | If-None-Match: * 641 | 642 | {"name":"test","@context":"http://remotestorage.io/spec/modules\ 643 | /myfavoritedrinks/drink"} 644 | 645 | And the server may respond with either a 201 Created or a 200 OK 646 | status: 647 | 648 | HTTP/1.1 201 Created 649 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 650 | ETag: "1382694045000" 651 | 652 | In case the document already exists on the server, it would respond 653 | with something like: 654 | 655 | HTTP/1.1 412 Precondition Failed 656 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 657 | ETag: "2182694048000" 658 | 659 | 12.6. Subsequent PUT 660 | 661 | A subsequent PUT may contain an 'If-Match' header referring to the 662 | ETag previously returned, like this: 663 | 664 | PUT /storage/michiel/myfavoritedrinks/test HTTP/1.1 665 | Host: 3pp.io:4439 666 | Content-Length: 91 667 | Origin: https://drinks-unhosted.5apps.com 668 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 669 | Content-Type: application/json; charset=UTF-8 670 | Referer: https://drinks-unhosted.5apps.com/ 671 | If-Match: "1382694045000" 672 | 673 | {"name":"test", "updated":true, "@context":"http://remotestorag\ 674 | e.io/spec/modules/myfavoritedrinks/drink"} 675 | 676 | And the server may respond with a 412 Precondition Failed or a 677 | 200 OK status: 678 | 679 | HTTP/1.1 200 OK 680 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 681 | ETag: "2182694048000" 682 | 683 | 12.7. GET 684 | 685 | A GET request would also include the bearer token, and optionally 686 | an If-None-Match header: 687 | 688 | GET /storage/michiel/myfavoritedrinks/test HTTP/1.1 689 | Host: 3pp.io:4439 690 | Origin: https://drinks-unhosted.5apps.com 691 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 692 | Referer: https://drinks-unhosted.5apps.com/ 693 | If-None-Match: "1382694045000", "1382694048000" 694 | 695 | And the server may respond with a 304 Not Modified status: 696 | 697 | HTTP/1.1 304 Not Modified 698 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 699 | ETag: "1382694048000" 700 | 701 | Or a 200 OK status, plus a response body: 702 | 703 | HTTP/1.1 200 OK 704 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 705 | Content-Type: application/json; charset=UTF-8 706 | Content-Length: 106 707 | ETag: "1382694048000" 708 | Cache-Control: no-cache 709 | 710 | {"name":"test", "updated":true, "@context":"http://remotestora\ 711 | ge.io/spec/modules/myfavoritedrinks/drink"} 712 | 713 | If the GET URL would have been "/storage/michiel/myfavoritedrinks/", 714 | a 200 OK response would have a folder description as the response 715 | body: 716 | 717 | HTTP/1.1 200 OK 718 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 719 | Content-Type: application/ld+json 720 | Content-Length: 171 721 | ETag: "1382694048000" 722 | Cache-Control: no-cache 723 | 724 | {"@context":"http://remotestorage.io/spec/folder-version","ite\ 725 | ms":{"test":{"ETag":"1382694048000","Content-Type":"application/json; \ 726 | charset=UTF-8","Content-Length":106,"Last-Modified":"Sat, 2 Jun 2018 1\ 727 | 5:58:23 GMT"}}} 728 | 729 | If the GET URL would have been a non-existing document like 730 | "/storage/michiel/myfavoritedrinks/x", the response would have a 404 731 | Not Found status, and no ETag header: 732 | 733 | HTTP/1.1 404 Not Found 734 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 735 | 736 | 12.8. DELETE 737 | 738 | A DELETE request may look like this: 739 | 740 | DELETE /storage/michiel/myfavoritedrinks/test HTTP/1.1 741 | Host: 3pp.io:4439 742 | Origin: https://drinks-unhosted.5apps.com 743 | Authorization: Bearer j2YnGtXjzzzHNjkd1CJxoQubA1o= 744 | Content-Type: application/json; charset=UTF-8 745 | Referer: https://drinks-unhosted.5apps.com/ 746 | If-Match: "1382694045000" 747 | 748 | And the server may respond with a 412 Precondition Failed or a 200 749 | OK status: 750 | 751 | HTTP/1.1 412 Precondition Failed 752 | Access-Control-Allow-Origin: https://drinks-unhosted.5apps.com 753 | ETag: "2182694048000" 754 | 755 | 756 | 757 | 13. Distributed versioning 758 | 759 | This section is non-normative, and is intended to explain some of 760 | the design choices concerning ETags and folder listings. At the 761 | same time it will hopefully help readers who intend to develop an 762 | application that uses remoteStorage as its per-user data storage. 763 | When multiple clients have read/write access to the same document, 764 | versioning conflicts may occur. For instance, client A may make 765 | a PUT request that changes the document from version 1 to version 766 | 2, after which client B may make a PUT request attempting to change 767 | the same document from version 1 to version 3. 768 | 769 | In this case, client B can add an 'If-Match: "1"' header, which 770 | would trigger a 412 Precondition Failed response code, since the 771 | current version ("2") does not match the version required as a 772 | condition by the header If-Match header ("1"). 773 | 774 | Client B is now aware of the conflict, and may consult the user, 775 | saying the update to version 3 failed. The user may then choose, 776 | through the user interface of client B, whether version 2 or 777 | version 3 should be kept, or maybe the document should be reverted 778 | on the server to version 1, or a merged version 4 is needed. Client 779 | B may then make a request that puts the document to the version the 780 | user wishes; this time setting an 'If-Match: "2"' header instead. 781 | 782 | Both client A and client B would periodically poll the root 783 | folder of each scope they have access to, to see if the version 784 | of the root folder changed. If it did, then one of the versions 785 | listed in there will necessarily have changed, and the client can 786 | make a GET request to that child folder or document, to obtain 787 | its latest version. 788 | 789 | Because an update in a document will result in a version change of 790 | its containing folder, and that change will propagate all the way 791 | to the root folder, it is not necessary to poll each document for 792 | changes individually. 793 | 794 | As an example, the root folder may contain 10 directories, 795 | each of which contain 10 directories, which each contain 10 796 | documents, so their paths would be for instance '/0/0/1', '/0/0/2', 797 | etcetera. Then one GET request to the root folder '/' will be 798 | enough to know if any of these 1000 documents has changed. 799 | 800 | Say document '/7/9/2' has changed; then the GET request to '/' will 801 | come back with a different ETag, and entry '7/' will have a 802 | different value in its JSON content. The client could then request 803 | '/7/', '/7/9/', and '/7/9/2' to narrow down the one document that 804 | caused the root folder's ETag to change. 805 | 806 | Note that the remoteStorage server does not get involved in the 807 | conflict resolution. It keeps the canonical current version at all 808 | times, and allows clients to make conditional GET and PUT requests, 809 | but it is up to whichever client discovers a given version 810 | conflict, to resolve it. 811 | 812 | 14. Security Considerations 813 | 814 | To prevent man-in-the-middle attacks, the use of HTTPS instead of 815 | http is important for both the interface itself and all end-points 816 | involved in WebFinger, OAuth, and (if present) the storage-first 817 | application launch dashboard. 818 | 819 | A malicious party could link to an application, but specifying a 820 | remoteStorage account address that it controls, thus tricking the 821 | user into using a trusted application to send sensitive data to the 822 | wrong remoteStorage server. To mitigate this, applications SHOULD 823 | clearly display to which remoteStorage server they are sending the 824 | user's data. 825 | 826 | Applications could request scopes that the user did not intend to 827 | give access to. The user SHOULD always be prompted to carefully 828 | review which scopes an application is requesting. 829 | 830 | An application may upload malicious HTML pages and then trick the 831 | user into visiting them, or upload malicious client-side scripts, 832 | that take advantage of being hosted on the user's domain name. The 833 | origin on which the remoteStorage server has its interface SHOULD 834 | therefore NOT be used for anything else, and the user SHOULD be 835 | warned not to visit any web pages on that origin. In particular, the 836 | OAuth dialog and launch dashboard or token revocation interface 837 | SHOULD be on a different origin than the remoteStorage interface. 838 | 839 | Where the use of bearer tokens is impractical, a user may choose to 840 | store documents on hard-to-guess URLs [CAPABILITIES] whose path 841 | after starts with '/public/', while sharing this URL 842 | only with the intended audience. That way, only parties who know the 843 | document's hard-to-guess URL, can access it. The server SHOULD 844 | therefore make an effort to detect and stop brute-force attacks that 845 | attempt to guess the location of such documents. 846 | 847 | The server SHOULD also detect and stop denial-of-service attacks 848 | that aim to overwhelm its interface with too much traffic. 849 | 850 | 15. IANA Considerations 851 | 852 | This document registers the following WebFinger properties: 853 | * "http://remotestorage.io/spec/version" 854 | * "http://tools.ietf.org/html/rfc6749#section-4.2" 855 | * "http://tools.ietf.org/html/rfc6750#section-2.3" 856 | * "http://tools.ietf.org/html/rfc7233" 857 | * "http://remotestorage.io/spec/web-authoring" 858 | 859 | 16. Acknowledgements 860 | 861 | The authors would like to thank everybody who contributed to the 862 | development of this protocol, including Kenny Bentley, Javier Diaz, 863 | Daniel Groeber, Bjarni Runar, Jan Wildeboer, Charles Schultz, Peter 864 | Svensson, Valer Mischenko, Michiel Leenaars, Jan-Christoph 865 | Borchardt, Garret Alfert, Sebastian Kippe, Max Wiehle, Melvin 866 | Carvalho, Martin Stadler, Geoffroy Couprie, Niklas Cathor, Marco 867 | Stahl, James Coglan, Ken Eucker, Daniel Brolund, elf Pavlik, Nick 868 | Jennings, Markus Sabadello, Steven te Brinke, Matthias Treydte, 869 | Rick van Rein, Mark Nottingham, Julian Reschke, Markus Lanthaler, 870 | and Markus Unterwaditzer, among many others. 871 | 872 | 17. References 873 | 874 | 17.1. Normative References 875 | 876 | [WORDS] 877 | Bradner, S., "Key words for use in RFCs to Indicate Requirement 878 | Levels", BCP 14, RFC 2119, March 1997. 879 | 880 | [IRI] 881 | Duerst, M., "Internationalized Resource Identifiers (IRIs)", 882 | RFC 3987, January 2005. 883 | 884 | [URI] 885 | Fielding, R., "Uniform Resource Identifier (URI): Generic 886 | Syntax", RFC 3986, January 2005. 887 | 888 | [WEBFINGER] 889 | Jones, P., Salguerio, G., Jones, M, and Smarr, J., 890 | "WebFinger", RFC7033, September 2013. 891 | 892 | [OAUTH] 893 | "Section 4.2: Implicit Grant", in: Hardt, D. (ed), "The OAuth 894 | 2.0 Authorization Framework", RFC6749, October 2012. 895 | 896 | [ORIGIN] 897 | "Section 4: Origin of a URI", in: Barth, A., "The Web Origin 898 | Concept", RFC6454, December 2011. 899 | 900 | 17.2. Informative References 901 | 902 | [HTTPS] 903 | Rescorla, E., "HTTP Over TLS", RFC2818, May 2000. 904 | 905 | [HTTP] 906 | Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1): 907 | Semantics and Content", RFC7231, June 2014. 908 | 909 | [COND] 910 | Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1): 911 | Conditional Requests", RFC7232, June 2014. 912 | 913 | [RANGE] 914 | Fielding et al., "Hypertext Transfer Protocol (HTTP/1.1): 915 | Conditional Requests", RFC7233, June 2014. 916 | 917 | [HTTP/2] 918 | M. Belshe, R. Peon, M. Thomson, Ed. "Hypertext Transfer Protocol 919 | Version 2 (HTTP/2)", RFC7540, May 2015. 920 | 921 | [JSON-LD] 922 | M. Sporny, G. Kellogg, M. Lanthaler, "JSON-LD 1.0", W3C 923 | Proposed Recommendation, 924 | http://www.w3.org/TR/2014/REC-json-ld-20140116/, January 2014. 925 | 926 | [CORS] 927 | van Kesteren, Anne (ed), "Cross-Origin Resource Sharing -- 928 | W3C Candidate Recommendation 29 January 2013", 929 | http://www.w3.org/TR/cors/, January 2013. 930 | 931 | [KERBEROS] 932 | C. Neuman et al., "The Kerberos Network Authentication Service 933 | (V5)", RFC4120, July 2005. 934 | 935 | [BEARER] 936 | M. Jones, D. Hardt, "The OAuth 2.0 Authorization Framework: 937 | Bearer Token Usage", RFC6750, October 2012. 938 | 939 | [AUTHORING] 940 | "Using remoteStorage for web authoring", reSite wiki, retrieved 941 | September 2014. https://github.com/michielbdejong/resite/wiki 942 | /Using-remoteStorage-for-web-authoring 943 | 944 | [CAPABILITIES] 945 | J. Tennison (ed.), "Good Practices for Capability URLs", 946 | http://www.w3.org/TR/capability-urls/, February 2014. 947 | 948 | 18. Authors' addresses 949 | 950 | Michiel B. de Jong 951 | (independent) 952 | 953 | Email: michiel@unhosted.org 954 | 955 | 956 | F. Kooman 957 | (independent) 958 | 959 | Email: fkooman@tuxed.net 960 | 961 | 962 | S. Kippe 963 | (independent) 964 | 965 | Email: sebastian@kip.pe 966 | 967 | --------------------------------------------------------------------------------