{"action":"created","comment":{"url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/comments/43396689","id":43396689,"diff_hunk":"@@ -0,0 +1,580 @@\n+---\n+title: \"Protocol: box\"\n+---\n+# Box Specification\n+\n+## Abstract\n+\n+A set of protocols to store files and folders on a VOLUME that is on AWS S3 and managed by a third party who doesn't need to be trusted.\n+\n+## Used services\n+\n+Qabel Box uses an Accounting server which controls the access to AWS S3. Every client who needs write access has to be authenticated by the server and then receives a set of credentials for direct access to the VOLUME.\n+Qabel Box also directly uses AWS S3 to store the blocks and metadata.\n+\n+## Accounting server\n+\n+The accounting server controls write access to the S3 bucket. Registered users (not identities!) can request temporary credentials for AWS.\n+All data is sent as JSON and UTF-8.\n+\n+### Required permissions for the AWS user\n+The accounting server uses an AWS user to create the temporary credentials. He must have the following permissions:\n+\n+* \"sts:GetFederationToken\" to generate the credentials\n+* \"s3:GetObject\" for read access\n+* \"s3:PutObject\" for uploading\n+* \"s3:ListBucket\" is used to find unreferenced files (not implemented)\n+\n+These permissions should only be granted for the Qabel box S3 bucket.\n+\n+### Login\n+The login method grants a new authentication token.\n+\n+* Ressource: /api/v0/auth/login\n+* Method: POST\n+* Request data: `{username: STR, password: STR}`\n+* Response data: `{key: STR}`\n+\n+The authentication token is used by including the header \"Authorization\" with the value \"Token \" concatenated with the key.\n+\n+For example: `Authorization: Token 70373def6f3766ab1782700cba4404`\n+\n+For every method except login, this authorization header is required.\n+\n+### Token\n+The token ressource controls the federation token.\n+\n+* Ressource: /api/v0/token\n+* Method: POST\n+* Request data: None\n+* Response data: `{AccessKeyId: STR, SecretAccessKey: STR, SessionToken: STR}`\n+\n+### Prefix\n+The prefix ressource controls all prefixes of the user. All prefixes are fully qualified URLs.\n+\n+Create a new prefix:\n+\n+* Ressource: /api/v0/prefix\n+* Method: POST\n+* Request data: None\n+* Response data: `{prefix: STR}`\n+\n+Get a list of available prefixes\n+\n+* Ressource: /api/v0/prefix\n+* Method: GET\n+* Request data: None\n+* Response data: `{prefixes: [STR]}`\n+\n+## Structure of a VOLUME\n+\n+A Volume consists of metadata files and blocks. Every VOLUME has a metadata file at VOLUME/\\<index\\> which is the starting point and contains references to other objects. All file names on S3 are UUIDs.\n+\n+All mtime values are seconds since epoch in UTC. Blocks are stored at VOLUME/blocks/.\n+\n+The metadata file stores information equivalent of this example JSON document, but stored in an SQLite database (the database schema is explained later):\n+\n+```\n+{\n+root: \"https://qabelbox.s3.amazonaws.com/users/b5911736-9ace-a799-8e34-dd9c17acff9a/\",\n+spec_version: 0,\n+version: {version: \"85bc5ead74c52df59c3abd3340ff9d6bd821acd61189950aec4f68c37b773a20\",\n+          time:  1445963627},\n+last_change_by: \"487a481f-4d93-cef0-4475-88ee576d37fd\",\n+shared: [\n+{ ref: \"aa8c3f39-edc5-00b0-ab8b-ba66d05b60db\",\n+  recipient: \"feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308\",\n+  type: \"READ\"},\n+{ ref: \"aa8c3f39-edc5-00b0-ab8b-ba66d05b60db\",\n+  recipient: \"fgah28991273814c9123987124f009893043ef75a0dbf3f4eba4a98eaa9b4e6a\",\n+  type: \"READ\"},\n+{ ref:\"8f5da4db-02ab-ca96-1824-3ba8d18a85be\",\n+  recipient: \"fgah28991273814c9123987124f009893043ef75a0dbf3f4eba4a98eaa9b4e6a\",\n+  type: \"READ\"}\n+],\n+files: [\n+{ name: \"foobar.jpg\", size: 6203434, mtime: 1445432325,\n+meta: null,\n+key: \"b43feebe528a56bb4f21ef3a8a617714aee2cabc0708c1702a98915ae852ad06\",\n+ref: \"0846C7C6-77F1-11E5-B21E-9CFF64691233\",\n+},\n+{ name: \"barfoo.txt\", size: 4568, mtime: 1445432120,\n+meta: \"a9c6ce30-418b-e292-83bc-769a8c72f600\",\n+key: \"042a77edb0d527816ddb3e74457d92e69302099881b9a3181a514696c0fc39bf\",\n+ref: \"8f5da4db-02ab-ca96-1824-3ba8d18a85be\"\n+}],\n+folders: [\n+{ name: \"some folder\",\n+key: \"fgah28991273814c9123987124f009893043ef75a0dbf3f4eba4a98eaa9b4e6a\",\n+ref: \"aa8c3f39-edc5-00b0-ab8b-ba66d05b60db\"\n+}],\n+externals: [\n+{ name: \"external share\",\n+owner: \"feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308\",\n+key: \"d570b7fcf9eda9daa648d5ec18ae04x9bd1f7b8d6acbd8764844df5aaae0ff91\",\n+url: \"https://other_bucket.s3.amazonaws.com/users/a3fdc333-a143-85aa-edbf-43adf3ff7315/b6e78ecb-176d-031c-d1d4-eed608ae6e12\"\n+}]\n+}\n+```\n+\n+### Directory Metadata [DM]\n+\n+\n+```\n+{\n+root: STR, // URL of the VOLUME, only in the index file\n+last_change_by: UUID, // ID of the device that made the last change\n+spec_version: INT,  // version of the VOLUME spec\n+\t\t\t\t\t// increment if migrations are needed\n+\t\t\t\t\t// and/or the files are incompatible between versions\n+version: {version: KEY, time: LONG}, // metadata version and time of change, time should not be trusted\n+shared: // description of all shares, only in the index file\n+{ shares* },\n+files: // list of files in this folder\n+[ file* ]\n+folders: // list of folders in this folder\n+[ folder* ]\n+externals: // list of external shares in this folder\n+[ external* ]\n+}\n+```\n+\n+Note that folders that are not \"index\" do not have the \"shared\"-key, as all information about shares in a VOLUME are stored in \"index\". The index file also has the URL of the VOLUME in the \"root\" attribute\n+\n+The version is a SHA-256 hash built with the following rule:\n+```\n+version(0) = SHA-256(0x00 || device-id)\n+version(n) = SHA-256(0x01 || version(n-1) || device-id)\n+```\n+\n+The device-id is unique for each client and a 128-bit value.\n+\n+### Shares\n+\n+The index is the path to the metadata file of the share.\n+\n+```\n+{\n+ref: STR // ref of the shared metadata file\n+recipient: KEY // public key of the recipient contact\n+type: \"READ\" // type is always READ for now\n+},\n+```\n+\n+### File Metadata [FM]\n+\n+A file only has its own FM, if it is shared in a single file share.\n+The FM is stored in the path referenced as \"meta\" in the file object and encrypted\n+with a new symmetric key\n+\n+```\n+{\n+name: STR, // filename\n+spec_version: INT,  // version of the VOLUME spec\n+size: LONG, // uncompressed file size\n+mtime: LONG, // modification time as seconds since epoch\n+key: KEY, // symmetric key for the block\n+block: STR // path to the block without the prefix \\<root\\>/blocks/\n+}\n+```\n+\n+### Objects\n+\n+File:\n+\n+```\n+{\n+name: STR, // object name,\n+size: LONG, // uncompressed file size\n+mtime: LONG, // modification time as seconds since epoch\n+meta: {ref: STR, // path to the FM, if it exists\n+       key: KEY} // symmetric key for the FM\n+key: KEY, // symmetric key for the block\n+block: STR // path to the block without the prefix \\<root\\>/blocks/\n+},\n+```\n+\n+Folder/Directory:\n+\n+```\n+{\n+name: STR // object name,\n+key: KEY // symmetric directory key\n+ref: STR // ref of the metadata file that contains information about the folder\n+},\n+```\n+\n+External:\n+\n+```\n+{\n+name: STR, // object name,\n+key: KEY // symmetric directory key\n+owner: STR, // public key of the owner of that VOLUME\n+url: STR // URL to the metadata file that contains information about the folder\n+},\n+```\n+\n+### dk - Directory Key\n+The directory key is stored in the directory object of the parent folder, the index\n+file DM is encrypted with the public key of the owner.\n+\n+### fk - File Key\n+File keys are stored in the directory metadata file\n+\n+### Qabel Identities\n+Identities have a public key **pub** and a private key **priv**\n+\n+### Device ID\n+Each client device has a unique ID which is a random generated UUID **devID**\n+\n+### Path to VOLUME/\\<index\\>\n+\\<index\\> is calculated from the sha256 of the owners private key. Take the first 128bit of it\n+and format them like the canonical form of a UUID.\n+This means: 8-4-4-4-12 hexadecimal digits.\n+Example: e5cceedc-c222-d549-6211-1b6c684e0b2a\n+\n+### Quota tracking\n+Quota tracking is done by AWS. The S3 service is configured to call an AWS Lambda method for each\n+s3:ObjectCreated and s3:ObjectRemoved in VOLUME/blocks. The Lambda method keeps track of the quota\n+by incrementing or decrementing the amount of used space saved in DynamoDB. The accounting server\n+then regularly requests all quota data and inserts it into his database. Clients can request\n+this information for their VOLUME by calling a REST method on the accounting server.\n+\n+\n+### Share notification drop message\n+The plaintext of the drop message is a JSON document with the url of the DM and the symmetric key.\n+\n+```\n+{\n+\turl: STR // url to the DM of the shared folder or the FM of the shared file\n+\tkey: KEY // symmetric directory key\n+}\n+```\n+\n+## Initializing a new VOLUME\n+\n+### Task\n+\n+Initialize a new VOLUME without any objects\n+\n+### Prerequisites\n+\n+* Valid federation token with write access to the VOLUME\n+* Device ID **devId0**\n+\n+### Process\n+\n+1. Create an empty DM\n+\n+\t```\n+\t{\n+\tpath: STR, // prefix of the volume\n+\tname: \"index\", // starting point of each VOLUME\n+\tspec_version: 0,\n+\tversion: SHA-256(0x00 || **devId0**),\n+\tlast_change_by: **devId0**\n+\tfiles: []\n+\tfolders: []\n+\texternals: []\n+\t}\n+\t```\n+1. Encrypt the file with **priv0** and upload it to VOLUME/\\<index\\>\n+\n+\n+## Uploading a new file\n+\n+### Task\n+\n+Upload a new file \"example.jpg\" from the client to the folder VOLUME/examples/.\n+\n+### Prerequisites\n+\n+* Valid federation token with write access to the VOLUME\n+\n+### Process\n+\n+1. Download VOLUME/\\<index\\> decrypt it with the users' private key **k0**\n+1. Find the folder \"examples\" in the index and retrieve the DM, decrypt it with the stored directory key **dk1**\n+1. Create a new symmetric key **fk0**\n+1. Encrypt the file with **fk0**\n+1. Generate a new UUID, this is the ref of the file\n+1. Upload the block to VOLUME/blocks/\\<uuid\\>, note the \"Date\" header from the response and use it as mtime\n+1. Insert the new object, including its **fk0**, into the metadata file, using the mtime from the response and the original file size in bytes as size\n+1. Set `last_change_by` to the user's device id\n+1. Encrypt the DM with **dk1** and upload it \n+\n+\n+## Browsing a share and downloading a file\n+\n+### Task\n+\n+Starting with only a VOLUME path and a qabel identity, let the user browse the whole VOLUME.\n+\n+### Prerequisites\n+\n+* URL of the VOLUME\n+\n+### Process\n+\n+1. Download VOLUME/\\<index\\> and decrypt it with the users' private key\n+1. Open the DM and show the directory listing to the user\n+1. If the user selects a directory or external share:\n+    1. Download the DM and decrypt it with the key stored in the parent DM\n+    1. Open the DM and show the directory listing to the user\n+1. If the user selects a file:\n+\t1. Download the referenced block\n+\t1. Read the symmetric file key **fk0** from the DM\n+\t1. Decrypt the block with **fk0**\n+\n+\n+## Deleting a file\n+\n+### Task\n+\n+Delete a file on the user's VOLUME.\n+\n+### Prerequisites\n+\n+* URL of the VOLUME\n+* Valid federation token with write access to the VOLUME\n+\n+### Process\n+\n+1. Download and decrypt the DM\n+1. Remove the file object from the DM, increment the version\n+1. Set `last_change_by` to the user's device id\n+1. Encrypt the DM and upload it, overwriting the old DM\n+1. Delete the block of the deleted file on S3\n+1. If the file object has a reference to a FM, delete the FM\n+\n+\n+## Updating a file\n+\n+### Task\n+\n+Update an existing file on the users VOLUME.\n+\n+### Prerequisites\n+\n+* URL of the VOLUME\n+* Valid federation token with write access to the VOLUME\n+\n+\n+### Process\n+\n+1. Download and decrypt the DM\n+1. Upload the file in a new block with a new UUID and a new key\n+1. Update the file object in the DM with the new ref and key, increment the version\n+1. Set `last_change_by` to user's device id\n+1. Encrypt the DM and upload it, overwriting the old DM\n+1. Update the FM, if one exists\n+1. Delete the block of the deleted file on S3\n+\n+\n+## Sharing a directory\n+\n+### Task\n+\n+Share a directory recursively to one or more contacts\n+\n+### Prerequisites\n+\n+* Path and directory key\n+* Valid federation token with write access to the VOLUME\n+* Contact info of the contacts\n+\n+\n+### Process\n+\n+1. Insert the share info in the index DM, increment the version, set the device id, encrypt and upload it\n+1. Notify the contacts about the new share with a drop message.\n+\n+\n+## Unsharing a directory\n+\n+### Task\n+\n+Remove a share to one or more contacts\n+\n+### Prerequisites\n+\n+* Path and directory key\n+* Valid federation token with write access to the VOLUME\n+* Public keys **pub1** of the contacts\n+\n+\n+### Process\n+\n+1. Remove the contacts' public keys from the share info of the folder in the index DM, set the device id and increment the version\n+1. Download recursively all DM\n+1. Upload an archive of all those DM with the name \"backup_\" + ref in the root directory (as a file object).\n+1. Delete recursively all DM from the share (See [Delete Multiple Objects](https://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html)\n+1. Wait until the deletion has propagated, check the progress by issuing HEAD requests for the files.\n+1. Create a new **dk*** for each DM in the share and insert them in their parents\n+1. Upload all the new DM, encrypted with their new **dk***, depth first\n+1. Check every uploaded DM with HEAD requests to ensure that no conflicts occurred.\n+1. Insert the new **dk*** into the parent directory of the share and upload the DM\n+1. Remove the backup file from the root directory\n+1. Send the new **dk*** of the share to all remaining contacts\n+\n+\n+## Sharing a single file\n+\n+### Task\n+\n+Share a single file to one or more contacts\n+\n+### Prerequisites\n+\n+* Valid federation token with write access to the VOLUME\n+* Contact info of the contacts\n+* DM of the parent folder\n+\n+### Process\n+\n+1. Create a new FM with the information from the DM\n+1. Encrypt it with a new directory key **dk1**\n+1. Insert the reference to the FM into the DM\n+1. Upload the FM and the DM\n+1. Insert the share info in the index DM and upload it\n+1. Notify the contacts about the new share with a drop message including **dk1**\n+\n+\n+# Handling conflicts\n+Conflicts can occour in the small timeframe between checking for changed DM and the\n+propagation delay after uploading the updated DM. A client has to check, after a reasonable delay,\n+if the changes were overwritten.\n+\n+1. Download the DM after 10s\n+1. Check if the version is the same as in the uploaded file\n+1. If a change is detected, repeat the original operation and insert into the DM, set the device, increment the version\n+1. Upload the merged DM and repeat.\n+\n+## Typical scenarios\n+\n+Client A uploads the DM first, Client B overwrites the DM.\n+\n+### Non conflicting changes\n+\n+Scenario: Client A creates a new object and client B creates a new object, the object have different names.\n+Solution: Client A inserts the object into the updated DM and uploads it\n+\n+### Conflicting changes\n+\n+Scenario: Client A deletes a file, client B changes the file (which uploads a new block)\n+Solution: Client A accepts the change from client B\n+\n+Scenario: Client A changes a file, client B deletes the file\n+Solution: Client A inserts the file again\n+\n+Scenario: Client A changes a file, client B changes the same file\n+Solution: Client A inserts the local version of the file with a deconflicted name by adding 'CONFLICT' and the date and time as suffix\n+Example: foobar.txt and foobar_CONFLICT_2015-10-23_19:33:23.txt\n+\n+\n+# SQLite Schema\n+\n+Schema for the SQLite3 database which is used as a directory metadata file (DM). The schema for\n+an FM will be included later.\n+The JSON documents can be directly translated into this schema.\n+\n+```SQL\n+/*\n+The meta table includes the values\n+ * root (only needed in the index)\n+ * last_change_by\n+*/\n+CREATE TABLE meta\n+(\n+       name             VARCHAR(24) PRIMARY KEY,\n+       value            TEXT\n+);\n+\n+/*\n+A one row table with only the current qabel-box specification version.\n+This version is 0 for now and should be checked everytime the databases is opened.\n+*/\n+CREATE TABLE spec_version\n+(\n+       version          INTEGER PRIMARY KEY\n+);\n+\n+/*\n+Current version and the current time. The time is only used for displaying,\n+it should not be trusted.\n+* 'id' is meaningless and only for record keeping purposes.\n+*/\n+CREATE TABLE version\n+(\n+       id               INTEGER PRIMARY KEY,\n+       version          BLOB NOT NULL,\n+       time             INTEGER NOT NULL\n+);\n+\n+/*\n+Table of all shares in the VOLUME.\n+* 'id' is meaningless and only for record keeping purposes.\n+* 'ref' is the name of the metadata file\n+* 'recipient' is the public key of the recipient\n+* 'type' is 0 for a read only share and 1 for a writable share (not implemented yet)\n+*/\n+CREATE TABLE shares\n+(\n+       id               INTEGER PRIMARY KEY,\n+       ref              VARCHAR(255) NOT NULL,\n+       recipient        BLOB NOT NULL,\n+       type             INT NOT NULL","path":"_docs/Qabel-Protocol-Box.md","position":527,"original_position":527,"commit_id":"d1b820c0034eda4e10e2d3cf04b1c03c3928a4fc","original_commit_id":"d1b820c0034eda4e10e2d3cf04b1c03c3928a4fc","user":{"login":"audax","id":858339,"avatar_url":"https://avatars.githubusercontent.com/u/858339?v=3","gravatar_id":"","url":"https://api.github.com/users/audax","html_url":"https://github.com/audax","followers_url":"https://api.github.com/users/audax/followers","following_url":"https://api.github.com/users/audax/following{/other_user}","gists_url":"https://api.github.com/users/audax/gists{/gist_id}","starred_url":"https://api.github.com/users/audax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/audax/subscriptions","organizations_url":"https://api.github.com/users/audax/orgs","repos_url":"https://api.github.com/users/audax/repos","events_url":"https://api.github.com/users/audax/events{/privacy}","received_events_url":"https://api.github.com/users/audax/received_events","type":"User","site_admin":false},"body":"Not is SQLite. LONG, INT, INTEGER and TINYINT are all the same. The names are just for documentation and the drivers.","created_at":"2015-10-29T15:07:06Z","updated_at":"2015-10-29T15:07:06Z","html_url":"https://github.com/Qabel/qabel.github.io/pull/101#discussion_r43396689","pull_request_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101","_links":{"self":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/comments/43396689"},"html":{"href":"https://github.com/Qabel/qabel.github.io/pull/101#discussion_r43396689"},"pull_request":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101"}}},"pull_request":{"url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101","id":48308634,"html_url":"https://github.com/Qabel/qabel.github.io/pull/101","diff_url":"https://github.com/Qabel/qabel.github.io/pull/101.diff","patch_url":"https://github.com/Qabel/qabel.github.io/pull/101.patch","issue_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/101","number":101,"state":"open","locked":false,"title":"Qabel box specification","user":{"login":"audax","id":858339,"avatar_url":"https://avatars.githubusercontent.com/u/858339?v=3","gravatar_id":"","url":"https://api.github.com/users/audax","html_url":"https://github.com/audax","followers_url":"https://api.github.com/users/audax/followers","following_url":"https://api.github.com/users/audax/following{/other_user}","gists_url":"https://api.github.com/users/audax/gists{/gist_id}","starred_url":"https://api.github.com/users/audax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/audax/subscriptions","organizations_url":"https://api.github.com/users/audax/orgs","repos_url":"https://api.github.com/users/audax/repos","events_url":"https://api.github.com/users/audax/events{/privacy}","received_events_url":"https://api.github.com/users/audax/received_events","type":"User","site_admin":false},"body":"\r\n* [x] Remove storage module\r\n* [x] Create qabel-accounting communication spec\r\n* [x] Describe requirements for AWS user\r\n* [x] Create qabel-box spec\r\n    * [x] SQLite Schema\r\n    * [x] Conflict solution\r\n    * [x] Calculate name for the index DM\r\n    * [x] Format for the \"new share\" drop message\r\n    * [x] Describe \"delete multiple\"\r\n    * [x] Quota keeping\r\n    * [x] Tasks\r\n      * [x] Create a new VOLUME\r\n      * [x] Upload a file\r\n      * [x] Delete a file\r\n      * [x] Update a file\r\n      * [x] Share a file\r\n      * [x] Share a folder\r\n* [x] Substitute \"VOLUME\" with a proper name -- \"VOLUME\" is proper enough.","created_at":"2015-10-21T09:43:48Z","updated_at":"2015-10-29T15:07:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ca3ec7c8f80700825c3ae7efdcb8c963ae1c8efb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101/commits","review_comments_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101/comments","review_comment_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/101/comments","statuses_url":"https://api.github.com/repos/Qabel/qabel.github.io/statuses/d1b820c0034eda4e10e2d3cf04b1c03c3928a4fc","head":{"label":"Qabel:box","ref":"box","sha":"d1b820c0034eda4e10e2d3cf04b1c03c3928a4fc","user":{"login":"Qabel","id":7711470,"avatar_url":"https://avatars.githubusercontent.com/u/7711470?v=3","gravatar_id":"","url":"https://api.github.com/users/Qabel","html_url":"https://github.com/Qabel","followers_url":"https://api.github.com/users/Qabel/followers","following_url":"https://api.github.com/users/Qabel/following{/other_user}","gists_url":"https://api.github.com/users/Qabel/gists{/gist_id}","starred_url":"https://api.github.com/users/Qabel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Qabel/subscriptions","organizations_url":"https://api.github.com/users/Qabel/orgs","repos_url":"https://api.github.com/users/Qabel/repos","events_url":"https://api.github.com/users/Qabel/events{/privacy}","received_events_url":"https://api.github.com/users/Qabel/received_events","type":"Organization","site_admin":false},"repo":{"id":21129265,"name":"qabel.github.io","full_name":"Qabel/qabel.github.io","owner":{"login":"Qabel","id":7711470,"avatar_url":"https://avatars.githubusercontent.com/u/7711470?v=3","gravatar_id":"","url":"https://api.github.com/users/Qabel","html_url":"https://github.com/Qabel","followers_url":"https://api.github.com/users/Qabel/followers","following_url":"https://api.github.com/users/Qabel/following{/other_user}","gists_url":"https://api.github.com/users/Qabel/gists{/gist_id}","starred_url":"https://api.github.com/users/Qabel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Qabel/subscriptions","organizations_url":"https://api.github.com/users/Qabel/orgs","repos_url":"https://api.github.com/users/Qabel/repos","events_url":"https://api.github.com/users/Qabel/events{/privacy}","received_events_url":"https://api.github.com/users/Qabel/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Qabel/qabel.github.io","description":"The Qabel documentation repository. The technical stuff can be found at our github.io page.","fork":false,"url":"https://api.github.com/repos/Qabel/qabel.github.io","forks_url":"https://api.github.com/repos/Qabel/qabel.github.io/forks","keys_url":"https://api.github.com/repos/Qabel/qabel.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Qabel/qabel.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Qabel/qabel.github.io/teams","hooks_url":"https://api.github.com/repos/Qabel/qabel.github.io/hooks","issue_events_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/Qabel/qabel.github.io/events","assignees_url":"https://api.github.com/repos/Qabel/qabel.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/Qabel/qabel.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/Qabel/qabel.github.io/tags","blobs_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Qabel/qabel.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/Qabel/qabel.github.io/languages","stargazers_url":"https://api.github.com/repos/Qabel/qabel.github.io/stargazers","contributors_url":"https://api.github.com/repos/Qabel/qabel.github.io/contributors","subscribers_url":"https://api.github.com/repos/Qabel/qabel.github.io/subscribers","subscription_url":"https://api.github.com/repos/Qabel/qabel.github.io/subscription","commits_url":"https://api.github.com/repos/Qabel/qabel.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/Qabel/qabel.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/Qabel/qabel.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/Qabel/qabel.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Qabel/qabel.github.io/merges","archive_url":"https://api.github.com/repos/Qabel/qabel.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Qabel/qabel.github.io/downloads","issues_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/Qabel/qabel.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/Qabel/qabel.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Qabel/qabel.github.io/labels{/name}","releases_url":"https://api.github.com/repos/Qabel/qabel.github.io/releases{/id}","created_at":"2014-06-23T14:28:44Z","updated_at":"2015-06-07T13:23:43Z","pushed_at":"2015-10-29T13:12:15Z","git_url":"git://github.com/Qabel/qabel.github.io.git","ssh_url":"git@github.com:Qabel/qabel.github.io.git","clone_url":"https://github.com/Qabel/qabel.github.io.git","svn_url":"https://github.com/Qabel/qabel.github.io","homepage":"https://qabel.github.io","size":53316,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":6,"mirror_url":null,"open_issues_count":12,"forks":6,"open_issues":12,"watchers":1,"default_branch":"master"}},"base":{"label":"Qabel:master","ref":"master","sha":"e514be4248c97e67972bdceaf6ef291b7a1e2759","user":{"login":"Qabel","id":7711470,"avatar_url":"https://avatars.githubusercontent.com/u/7711470?v=3","gravatar_id":"","url":"https://api.github.com/users/Qabel","html_url":"https://github.com/Qabel","followers_url":"https://api.github.com/users/Qabel/followers","following_url":"https://api.github.com/users/Qabel/following{/other_user}","gists_url":"https://api.github.com/users/Qabel/gists{/gist_id}","starred_url":"https://api.github.com/users/Qabel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Qabel/subscriptions","organizations_url":"https://api.github.com/users/Qabel/orgs","repos_url":"https://api.github.com/users/Qabel/repos","events_url":"https://api.github.com/users/Qabel/events{/privacy}","received_events_url":"https://api.github.com/users/Qabel/received_events","type":"Organization","site_admin":false},"repo":{"id":21129265,"name":"qabel.github.io","full_name":"Qabel/qabel.github.io","owner":{"login":"Qabel","id":7711470,"avatar_url":"https://avatars.githubusercontent.com/u/7711470?v=3","gravatar_id":"","url":"https://api.github.com/users/Qabel","html_url":"https://github.com/Qabel","followers_url":"https://api.github.com/users/Qabel/followers","following_url":"https://api.github.com/users/Qabel/following{/other_user}","gists_url":"https://api.github.com/users/Qabel/gists{/gist_id}","starred_url":"https://api.github.com/users/Qabel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Qabel/subscriptions","organizations_url":"https://api.github.com/users/Qabel/orgs","repos_url":"https://api.github.com/users/Qabel/repos","events_url":"https://api.github.com/users/Qabel/events{/privacy}","received_events_url":"https://api.github.com/users/Qabel/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Qabel/qabel.github.io","description":"The Qabel documentation repository. The technical stuff can be found at our github.io page.","fork":false,"url":"https://api.github.com/repos/Qabel/qabel.github.io","forks_url":"https://api.github.com/repos/Qabel/qabel.github.io/forks","keys_url":"https://api.github.com/repos/Qabel/qabel.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Qabel/qabel.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Qabel/qabel.github.io/teams","hooks_url":"https://api.github.com/repos/Qabel/qabel.github.io/hooks","issue_events_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/Qabel/qabel.github.io/events","assignees_url":"https://api.github.com/repos/Qabel/qabel.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/Qabel/qabel.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/Qabel/qabel.github.io/tags","blobs_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Qabel/qabel.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/Qabel/qabel.github.io/languages","stargazers_url":"https://api.github.com/repos/Qabel/qabel.github.io/stargazers","contributors_url":"https://api.github.com/repos/Qabel/qabel.github.io/contributors","subscribers_url":"https://api.github.com/repos/Qabel/qabel.github.io/subscribers","subscription_url":"https://api.github.com/repos/Qabel/qabel.github.io/subscription","commits_url":"https://api.github.com/repos/Qabel/qabel.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/Qabel/qabel.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/Qabel/qabel.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/Qabel/qabel.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/Qabel/qabel.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Qabel/qabel.github.io/merges","archive_url":"https://api.github.com/repos/Qabel/qabel.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Qabel/qabel.github.io/downloads","issues_url":"https://api.github.com/repos/Qabel/qabel.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/Qabel/qabel.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/Qabel/qabel.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/Qabel/qabel.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Qabel/qabel.github.io/labels{/name}","releases_url":"https://api.github.com/repos/Qabel/qabel.github.io/releases{/id}","created_at":"2014-06-23T14:28:44Z","updated_at":"2015-06-07T13:23:43Z","pushed_at":"2015-10-29T13:12:15Z","git_url":"git://github.com/Qabel/qabel.github.io.git","ssh_url":"git@github.com:Qabel/qabel.github.io.git","clone_url":"https://github.com/Qabel/qabel.github.io.git","svn_url":"https://github.com/Qabel/qabel.github.io","homepage":"https://qabel.github.io","size":53316,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":6,"mirror_url":null,"open_issues_count":12,"forks":6,"open_issues":12,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101"},"html":{"href":"https://github.com/Qabel/qabel.github.io/pull/101"},"issue":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/issues/101"},"comments":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/issues/101/comments"},"review_comments":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101/comments"},"review_comment":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/pulls/101/commits"},"statuses":{"href":"https://api.github.com/repos/Qabel/qabel.github.io/statuses/d1b820c0034eda4e10e2d3cf04b1c03c3928a4fc"}}}}