{"action":"created","comment":{"url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/comments/112455284","pull_request_review_id":33770649,"id":112455284,"diff_hunk":"@@ -3,3 +3,4 @@ coverage==4.3.4\n Sphinx==1.5.2\n sphinx-autobuild==0.6.0\n sphinx-rtd-theme==0.1.9\n+mypy==0.501","path":"requirements-dev.txt","position":4,"original_position":4,"commit_id":"6850abbaa572ebe151b96b1341d0ff7dc8452bda","original_commit_id":"6850abbaa572ebe151b96b1341d0ff7dc8452bda","user":{"login":"jseppi","id":697848,"avatar_url":"https://avatars1.githubusercontent.com/u/697848?v=3","gravatar_id":"","url":"https://api.github.com/users/jseppi","html_url":"https://github.com/jseppi","followers_url":"https://api.github.com/users/jseppi/followers","following_url":"https://api.github.com/users/jseppi/following{/other_user}","gists_url":"https://api.github.com/users/jseppi/gists{/gist_id}","starred_url":"https://api.github.com/users/jseppi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jseppi/subscriptions","organizations_url":"https://api.github.com/users/jseppi/orgs","repos_url":"https://api.github.com/users/jseppi/repos","events_url":"https://api.github.com/users/jseppi/events{/privacy}","received_events_url":"https://api.github.com/users/jseppi/received_events","type":"User","site_admin":false},"body":"why does mypy have weird versions like this o_O","created_at":"2017-04-20T13:39:00Z","updated_at":"2017-04-20T13:41:29Z","html_url":"https://github.com/18F/cg-django-uaa/pull/24#discussion_r112455284","pull_request_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24","_links":{"self":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/comments/112455284"},"html":{"href":"https://github.com/18F/cg-django-uaa/pull/24#discussion_r112455284"},"pull_request":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24"}}},"pull_request":{"url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24","id":116579841,"html_url":"https://github.com/18F/cg-django-uaa/pull/24","diff_url":"https://github.com/18F/cg-django-uaa/pull/24.diff","patch_url":"https://github.com/18F/cg-django-uaa/pull/24.patch","issue_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/24","number":24,"state":"open","locked":false,"title":"Add UaaRefreshMiddleware","user":{"login":"toolness","id":124687,"avatar_url":"https://avatars1.githubusercontent.com/u/124687?v=3","gravatar_id":"","url":"https://api.github.com/users/toolness","html_url":"https://github.com/toolness","followers_url":"https://api.github.com/users/toolness/followers","following_url":"https://api.github.com/users/toolness/following{/other_user}","gists_url":"https://api.github.com/users/toolness/gists{/gist_id}","starred_url":"https://api.github.com/users/toolness/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/toolness/subscriptions","organizations_url":"https://api.github.com/users/toolness/orgs","repos_url":"https://api.github.com/users/toolness/repos","events_url":"https://api.github.com/users/toolness/events{/privacy}","received_events_url":"https://api.github.com/users/toolness/received_events","type":"User","site_admin":false},"body":"This is an attempt to fix #22 by adding a new `UaaRefreshMiddleware` that checks to see if the user's access token has expired, and, if so, attempts to renew it using a [refresh request](http://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens).  If that fails, then the user is logged out.\r\n\r\n## Notes\r\n\r\n* Django session expiry is no longer defined based on the access token expiry because I think it's important for the Django app to be in control of session expiry as much as possible without violating compliance requirements. For example, even if cloud.gov eventually needs to change its access token lifetime to just one minute, it'd be quite unfortunate if we forcibly expired user sessions after just one minute! So instead, we let the Django app dictate how long it'd like sessions to last, and we only forcibly logout a user if *both* their access token has expired *and* we were unable to renew it.\r\n\r\n  This approach is useful because refresh tokens are almost guaranteed to have a longer lifetime than access tokens--for example, at the time of this writing, access tokens have a lifetime of 10-15 minutes, but refresh tokens have a lifetime of 24 hours.\r\n\r\n* My one concern with this middleware approach is that if multiple requests are made by a user's browser, e.g. because dynamically-generated static assets are referenced by a page, or because a page makes multiple simultaneous API calls to a self-hosted API, then this middleware will be triggered multiple times. The good news is that I found in practice that cloud.gov UAA is OK with this: using a refresh token once doesn't render it invalid for subsequent uses. The bad news is that we'd still be making multiple identical requests to refresh the same access token, which isn't terribly efficient.\r\n\r\n  I'm not sure if there are any decent ways around this that don't inflict additional burdens on the client Django app.  For instance, another possibility might be to make a scheduled task that iterates through all active sessions and expires them if cloud.gov thinks their related access tokens shouldn't be renewed--but this would require the client to have a task scheduler running.\r\n\r\n## To do\r\n\r\n- [x] Documentation\r\n- [x] Tests for `middleware.py`\r\n- [x] It might be useful to modify the fake cloud.gov endpoints to support token renewal. We might even want these endpoints to issue _really_ short-lived access-tokens, e.g. ones with 1 minute lifespans, to ensure that the renewal code is exercised a lot during development.\r\n","created_at":"2017-04-19T15:44:14Z","updated_at":"2017-04-20T13:41:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"689f5099693d63f8903c03a5946f052ed5dc30a4","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24/commits","review_comments_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24/comments","review_comment_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/24/comments","statuses_url":"https://api.github.com/repos/18F/cg-django-uaa/statuses/6850abbaa572ebe151b96b1341d0ff7dc8452bda","head":{"label":"18F:refresh-token","ref":"refresh-token","sha":"6850abbaa572ebe151b96b1341d0ff7dc8452bda","user":{"login":"18F","id":6233994,"avatar_url":"https://avatars3.githubusercontent.com/u/6233994?v=3","gravatar_id":"","url":"https://api.github.com/users/18F","html_url":"https://github.com/18F","followers_url":"https://api.github.com/users/18F/followers","following_url":"https://api.github.com/users/18F/following{/other_user}","gists_url":"https://api.github.com/users/18F/gists{/gist_id}","starred_url":"https://api.github.com/users/18F/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/18F/subscriptions","organizations_url":"https://api.github.com/users/18F/orgs","repos_url":"https://api.github.com/users/18F/repos","events_url":"https://api.github.com/users/18F/events{/privacy}","received_events_url":"https://api.github.com/users/18F/received_events","type":"Organization","site_admin":false},"repo":{"id":80160281,"name":"cg-django-uaa","full_name":"18F/cg-django-uaa","owner":{"login":"18F","id":6233994,"avatar_url":"https://avatars3.githubusercontent.com/u/6233994?v=3","gravatar_id":"","url":"https://api.github.com/users/18F","html_url":"https://github.com/18F","followers_url":"https://api.github.com/users/18F/followers","following_url":"https://api.github.com/users/18F/following{/other_user}","gists_url":"https://api.github.com/users/18F/gists{/gist_id}","starred_url":"https://api.github.com/users/18F/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/18F/subscriptions","organizations_url":"https://api.github.com/users/18F/orgs","repos_url":"https://api.github.com/users/18F/repos","events_url":"https://api.github.com/users/18F/events{/privacy}","received_events_url":"https://api.github.com/users/18F/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/18F/cg-django-uaa","description":"A cloud.gov UAA authentication backend for Django","fork":false,"url":"https://api.github.com/repos/18F/cg-django-uaa","forks_url":"https://api.github.com/repos/18F/cg-django-uaa/forks","keys_url":"https://api.github.com/repos/18F/cg-django-uaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/18F/cg-django-uaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/18F/cg-django-uaa/teams","hooks_url":"https://api.github.com/repos/18F/cg-django-uaa/hooks","issue_events_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/events{/number}","events_url":"https://api.github.com/repos/18F/cg-django-uaa/events","assignees_url":"https://api.github.com/repos/18F/cg-django-uaa/assignees{/user}","branches_url":"https://api.github.com/repos/18F/cg-django-uaa/branches{/branch}","tags_url":"https://api.github.com/repos/18F/cg-django-uaa/tags","blobs_url":"https://api.github.com/repos/18F/cg-django-uaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/18F/cg-django-uaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/18F/cg-django-uaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/18F/cg-django-uaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/18F/cg-django-uaa/statuses/{sha}","languages_url":"https://api.github.com/repos/18F/cg-django-uaa/languages","stargazers_url":"https://api.github.com/repos/18F/cg-django-uaa/stargazers","contributors_url":"https://api.github.com/repos/18F/cg-django-uaa/contributors","subscribers_url":"https://api.github.com/repos/18F/cg-django-uaa/subscribers","subscription_url":"https://api.github.com/repos/18F/cg-django-uaa/subscription","commits_url":"https://api.github.com/repos/18F/cg-django-uaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/18F/cg-django-uaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/18F/cg-django-uaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/18F/cg-django-uaa/contents/{+path}","compare_url":"https://api.github.com/repos/18F/cg-django-uaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/18F/cg-django-uaa/merges","archive_url":"https://api.github.com/repos/18F/cg-django-uaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/18F/cg-django-uaa/downloads","issues_url":"https://api.github.com/repos/18F/cg-django-uaa/issues{/number}","pulls_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls{/number}","milestones_url":"https://api.github.com/repos/18F/cg-django-uaa/milestones{/number}","notifications_url":"https://api.github.com/repos/18F/cg-django-uaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/18F/cg-django-uaa/labels{/name}","releases_url":"https://api.github.com/repos/18F/cg-django-uaa/releases{/id}","deployments_url":"https://api.github.com/repos/18F/cg-django-uaa/deployments","created_at":"2017-01-26T22:06:11Z","updated_at":"2017-01-30T16:08:21Z","pushed_at":"2017-04-19T23:18:51Z","git_url":"git://github.com/18F/cg-django-uaa.git","ssh_url":"git@github.com:18F/cg-django-uaa.git","clone_url":"https://github.com/18F/cg-django-uaa.git","svn_url":"https://github.com/18F/cg-django-uaa","homepage":"http://cg-django-uaa.readthedocs.io/","size":938,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":7,"forks":0,"open_issues":7,"watchers":0,"default_branch":"master"}},"base":{"label":"18F:master","ref":"master","sha":"ce9bfea3b854d6fa11d9f900081ac79a34edb759","user":{"login":"18F","id":6233994,"avatar_url":"https://avatars3.githubusercontent.com/u/6233994?v=3","gravatar_id":"","url":"https://api.github.com/users/18F","html_url":"https://github.com/18F","followers_url":"https://api.github.com/users/18F/followers","following_url":"https://api.github.com/users/18F/following{/other_user}","gists_url":"https://api.github.com/users/18F/gists{/gist_id}","starred_url":"https://api.github.com/users/18F/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/18F/subscriptions","organizations_url":"https://api.github.com/users/18F/orgs","repos_url":"https://api.github.com/users/18F/repos","events_url":"https://api.github.com/users/18F/events{/privacy}","received_events_url":"https://api.github.com/users/18F/received_events","type":"Organization","site_admin":false},"repo":{"id":80160281,"name":"cg-django-uaa","full_name":"18F/cg-django-uaa","owner":{"login":"18F","id":6233994,"avatar_url":"https://avatars3.githubusercontent.com/u/6233994?v=3","gravatar_id":"","url":"https://api.github.com/users/18F","html_url":"https://github.com/18F","followers_url":"https://api.github.com/users/18F/followers","following_url":"https://api.github.com/users/18F/following{/other_user}","gists_url":"https://api.github.com/users/18F/gists{/gist_id}","starred_url":"https://api.github.com/users/18F/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/18F/subscriptions","organizations_url":"https://api.github.com/users/18F/orgs","repos_url":"https://api.github.com/users/18F/repos","events_url":"https://api.github.com/users/18F/events{/privacy}","received_events_url":"https://api.github.com/users/18F/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/18F/cg-django-uaa","description":"A cloud.gov UAA authentication backend for Django","fork":false,"url":"https://api.github.com/repos/18F/cg-django-uaa","forks_url":"https://api.github.com/repos/18F/cg-django-uaa/forks","keys_url":"https://api.github.com/repos/18F/cg-django-uaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/18F/cg-django-uaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/18F/cg-django-uaa/teams","hooks_url":"https://api.github.com/repos/18F/cg-django-uaa/hooks","issue_events_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/events{/number}","events_url":"https://api.github.com/repos/18F/cg-django-uaa/events","assignees_url":"https://api.github.com/repos/18F/cg-django-uaa/assignees{/user}","branches_url":"https://api.github.com/repos/18F/cg-django-uaa/branches{/branch}","tags_url":"https://api.github.com/repos/18F/cg-django-uaa/tags","blobs_url":"https://api.github.com/repos/18F/cg-django-uaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/18F/cg-django-uaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/18F/cg-django-uaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/18F/cg-django-uaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/18F/cg-django-uaa/statuses/{sha}","languages_url":"https://api.github.com/repos/18F/cg-django-uaa/languages","stargazers_url":"https://api.github.com/repos/18F/cg-django-uaa/stargazers","contributors_url":"https://api.github.com/repos/18F/cg-django-uaa/contributors","subscribers_url":"https://api.github.com/repos/18F/cg-django-uaa/subscribers","subscription_url":"https://api.github.com/repos/18F/cg-django-uaa/subscription","commits_url":"https://api.github.com/repos/18F/cg-django-uaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/18F/cg-django-uaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/18F/cg-django-uaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/18F/cg-django-uaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/18F/cg-django-uaa/contents/{+path}","compare_url":"https://api.github.com/repos/18F/cg-django-uaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/18F/cg-django-uaa/merges","archive_url":"https://api.github.com/repos/18F/cg-django-uaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/18F/cg-django-uaa/downloads","issues_url":"https://api.github.com/repos/18F/cg-django-uaa/issues{/number}","pulls_url":"https://api.github.com/repos/18F/cg-django-uaa/pulls{/number}","milestones_url":"https://api.github.com/repos/18F/cg-django-uaa/milestones{/number}","notifications_url":"https://api.github.com/repos/18F/cg-django-uaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/18F/cg-django-uaa/labels{/name}","releases_url":"https://api.github.com/repos/18F/cg-django-uaa/releases{/id}","deployments_url":"https://api.github.com/repos/18F/cg-django-uaa/deployments","created_at":"2017-01-26T22:06:11Z","updated_at":"2017-01-30T16:08:21Z","pushed_at":"2017-04-19T23:18:51Z","git_url":"git://github.com/18F/cg-django-uaa.git","ssh_url":"git@github.com:18F/cg-django-uaa.git","clone_url":"https://github.com/18F/cg-django-uaa.git","svn_url":"https://github.com/18F/cg-django-uaa","homepage":"http://cg-django-uaa.readthedocs.io/","size":938,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":7,"forks":0,"open_issues":7,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24"},"html":{"href":"https://github.com/18F/cg-django-uaa/pull/24"},"issue":{"href":"https://api.github.com/repos/18F/cg-django-uaa/issues/24"},"comments":{"href":"https://api.github.com/repos/18F/cg-django-uaa/issues/24/comments"},"review_comments":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24/comments"},"review_comment":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/18F/cg-django-uaa/pulls/24/commits"},"statuses":{"href":"https://api.github.com/repos/18F/cg-django-uaa/statuses/6850abbaa572ebe151b96b1341d0ff7dc8452bda"}}}}
{"actor":{"display_login":"jseppi"}}