{"action":"created","issue":{"url":"https://api.github.com/repos/syncany/syncany/issues/426","labels_url":"https://api.github.com/repos/syncany/syncany/issues/426/labels{/name}","comments_url":"https://api.github.com/repos/syncany/syncany/issues/426/comments","events_url":"https://api.github.com/repos/syncany/syncany/issues/426/events","html_url":"https://github.com/syncany/syncany/pull/426","id":65441182,"number":426,"title":"Automatic OAuth token handling","user":{"login":"cr0","id":854965,"avatar_url":"https://avatars.githubusercontent.com/u/854965?v=3","gravatar_id":"","url":"https://api.github.com/users/cr0","html_url":"https://github.com/cr0","followers_url":"https://api.github.com/users/cr0/followers","following_url":"https://api.github.com/users/cr0/following{/other_user}","gists_url":"https://api.github.com/users/cr0/gists{/gist_id}","starred_url":"https://api.github.com/users/cr0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cr0/subscriptions","organizations_url":"https://api.github.com/users/cr0/orgs","repos_url":"https://api.github.com/users/cr0/repos","events_url":"https://api.github.com/users/cr0/events{/privacy}","received_events_url":"https://api.github.com/users/cr0/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/syncany/syncany/labels/status:in-progress","name":"status:in-progress","color":"eeeeee"},{"url":"https://api.github.com/repos/syncany/syncany/labels/type:enhancement","name":"type:enhancement","color":"fad8c7"},{"url":"https://api.github.com/repos/syncany/syncany/labels/what:core","name":"what:core","color":"eb6420"},{"url":"https://api.github.com/repos/syncany/syncany/labels/what:plugin","name":"what:plugin","color":"207de5"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":7,"created_at":"2015-03-31T12:40:23Z","updated_at":"2015-04-05T17:28:01Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/syncany/syncany/pulls/426","html_url":"https://github.com/syncany/syncany/pull/426","diff_url":"https://github.com/syncany/syncany/pull/426.diff","patch_url":"https://github.com/syncany/syncany/pull/426.patch"},"body":"This PR makes the OAuth process much more user friendly by automatically receiving tokens from a provider. This is much more common than copy-pasting a token from website (c.f. several email clients which use google's mail API).\r\n\r\n*Some OAuth background*\r\nThere are several possibilities how OAuth can work. One is that a provider forwards an access token through a callback URL. Sometimes, a provider uses the query parameters (the part behind the `?`) but in order to prevent token stealing (or suppress the logging of a token) by a server provider it is also possible to use the hash or fragment path of an URL (the part behind a `#`). The fragment path is only delivered to a client (=browser), not a web server. It could be possible that some providers only support OAuth using callback URLs, hence we need to handle such cases. \r\n\r\n*How does it work?*\r\nIf Syncany detects an OAuth enabled `TransferSetting` plugin, it starts up a web server on a random (or fixed) port and generates a callback URL which should be used during the OAuth process. In order to be flexible for different OAuth processes, the token server uses so-called `Interceptors` and `Extractors`. `Interceptors` handle different HTTP requests and react upon them. `Extractors` are used to actually extract the token from the callback URL. Of course, the process supports protection measures against CSRF attacks, namely the `state` parameter from the OAuth RFC.\r\n\r\nI created [a gist](https://gist.github.com/cr0/2ee0328d54b016d259a3) with an OAuthWebListener enabled DropboxTransferSettings class.\r\n\r\nThe process flow is as follows. First, a `OAuthTokenWebListener` is build on the basis of an already existing `OAuthGenerator`. It is possible to use a different first interceptor and an token extractor. I commented the sample code below because I think it's easy to get the idea on the basis of working code.\r\n```java\r\nOAuthTokenWebListener twl = OAuthTokenWebListener\r\n\t.forId(\"testSite\") // each token listener needs an individual id\r\n\t.setTokenInterceptor(new OAuthTokenInterceptors.RedirectTokenInterceptor())\r\n\t.setTokenExtractor(new OAuthTokenExtractors.NamedQueryTokenExtractor(\"access_token\", \"state\"))\r\n\t.build();\r\n\r\nURI baseUri = twl.start(); // start the token listener server\r\nFuture<OAuthTokenFinish> submittedFutureToken = twl.getToken(); // wait for a token and return a future reference\r\nOAuthTokenFinish submittedToken = submittedFutureToken.get(); // get the actual token. waits if the token hasn't already been transmitted. \r\n```\r\nSince Dropbox does not use the fragment path of the url to transmit the token, it is sufficient to simply redirect the response to the extractor (thus the `RedirectTokenInterceptor`). The token is embedded in the query parameters named as `access_token`, hence we use a `NamedQueryTokenExtractor(\"access_token\", \"state\")`. To protect the OAuth process, Dropbox supports a `state` parameter.\r\n\r\nAs you can see from the gist, there is very little to do in order to enable the listener service. However, the benefit for an enduser is huge!\r\n\r\n*And because screenshots are always better than text*\r\n\r\nProcess start:\r\n![screen shot 2015-03-31 at 13 45 36](https://cloud.githubusercontent.com/assets/854965/6918887/7a0367e6-d7b2-11e4-857d-e92156412a5b.png)\r\n\r\nUser accepts the authorization request:\r\n![screen shot 2015-03-31 at 13 46 16](https://cloud.githubusercontent.com/assets/854965/6918938/db4de92c-d7b2-11e4-874a-7e35338b7285.png)\r\n\r\nUser disallow the authorization request:\r\n![screen shot 2015-03-31 at 13 49 32](https://cloud.githubusercontent.com/assets/854965/6918940/e1b0ef4e-d7b2-11e4-8a4f-f30bd11e8998.png)\r\n\r\nThis PR does not add additional runtime dependencies, although, `htmlunit` is required to run the tests."},"comment":{"url":"https://api.github.com/repos/syncany/syncany/issues/comments/89815325","html_url":"https://github.com/syncany/syncany/pull/426#issuecomment-89815325","issue_url":"https://api.github.com/repos/syncany/syncany/issues/426","id":89815325,"user":{"login":"binwiederhier","id":664597,"avatar_url":"https://avatars.githubusercontent.com/u/664597?v=3","gravatar_id":"","url":"https://api.github.com/users/binwiederhier","html_url":"https://github.com/binwiederhier","followers_url":"https://api.github.com/users/binwiederhier/followers","following_url":"https://api.github.com/users/binwiederhier/following{/other_user}","gists_url":"https://api.github.com/users/binwiederhier/gists{/gist_id}","starred_url":"https://api.github.com/users/binwiederhier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/binwiederhier/subscriptions","organizations_url":"https://api.github.com/users/binwiederhier/orgs","repos_url":"https://api.github.com/users/binwiederhier/repos","events_url":"https://api.github.com/users/binwiederhier/events{/privacy}","received_events_url":"https://api.github.com/users/binwiederhier/received_events","type":"User","site_admin":false},"created_at":"2015-04-05T17:28:01Z","updated_at":"2015-04-05T17:28:01Z","body":"@cr0 Looks good to me. I like the `--headless` option. Good idea! \r\n\r\n(a) I'd love a bit more JavaDoc-ing on the new classes if it's not too much to ask\r\n(b) Can you add the `--headless` option to the bash completion script and the help/man page?\r\n(c) This does not break any of the existing plugins, right? It shouldn't if I understand the code correctly.\r\n(d) Merge if you want to. \r\n\r\n@dmexs I think Amazon will just redirect to that URL via HTTP \"Location:\". I don't think it has to be publicly accessible. Of course I haven't checked ..."}}