{"action":"created","comment":{"url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/comments/58647977","id":58647977,"diff_hunk":"@@ -55,3 +56,42 @@ def unauthorized_token():\n                         'message': 'please send your authentication token'})\n     response.status_code = 401\n     return response\n+\n+\n+def permission_required(permission):\n+    \"\"\"Route decorator to test user authorizations.\n+\n+    The decorator should be applied *after* the authentication decorator.\n+    For example::\n+\n+        @api.route('/secure')\n+        @auth.permission_required(Permission.ADMIN_USER)\n+        @token_auth.login_required\n+        def hello():\n+            pass\n+\n+    Response with a 403 response if authorization fails. If the user is\n+    not set (because the username/password was blank, or because a\n+    token_required decorator was not applied, then a 401 response is sent.\n+    Authorization requires authentication.\n+    \"\"\"\n+    def decorator(f):\n+        @wraps(f)\n+        def decorated_function(*args, **kwargs):\n+            if g.get('user', None) is None:\n+                # user not authenticated\n+                response = jsonify({'status': 401, 'error': 'unauthenticated',","path":"app/auth.py","position":43,"original_position":43,"commit_id":"6041f318ab047b7d448f59a6abbd3382b9f19f6e","original_commit_id":"6041f318ab047b7d448f59a6abbd3382b9f19f6e","user":{"login":"jmatt","id":5917,"avatar_url":"https://avatars.githubusercontent.com/u/5917?v=3","gravatar_id":"","url":"https://api.github.com/users/jmatt","html_url":"https://github.com/jmatt","followers_url":"https://api.github.com/users/jmatt/followers","following_url":"https://api.github.com/users/jmatt/following{/other_user}","gists_url":"https://api.github.com/users/jmatt/gists{/gist_id}","starred_url":"https://api.github.com/users/jmatt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jmatt/subscriptions","organizations_url":"https://api.github.com/users/jmatt/orgs","repos_url":"https://api.github.com/users/jmatt/repos","events_url":"https://api.github.com/users/jmatt/events{/privacy}","received_events_url":"https://api.github.com/users/jmatt/received_events","type":"User","site_admin":false},"body":"This can be simplified. I'd move the status_code into the dictionary then just return the jsonify'ed dictionary.","created_at":"2016-04-06T03:51:32Z","updated_at":"2016-04-06T03:51:32Z","html_url":"https://github.com/lsst-sqre/ltd-keeper/pull/3#discussion_r58647977","pull_request_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3","_links":{"self":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/comments/58647977"},"html":{"href":"https://github.com/lsst-sqre/ltd-keeper/pull/3#discussion_r58647977"},"pull_request":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3"}}},"pull_request":{"url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3","id":64577659,"html_url":"https://github.com/lsst-sqre/ltd-keeper/pull/3","diff_url":"https://github.com/lsst-sqre/ltd-keeper/pull/3.diff","patch_url":"https://github.com/lsst-sqre/ltd-keeper/pull/3.patch","issue_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/3","number":3,"state":"open","locked":false,"title":"DM-5645: Add fine-grained authorization to ltd-keeper users","user":{"login":"jonathansick","id":349384,"avatar_url":"https://avatars.githubusercontent.com/u/349384?v=3","gravatar_id":"","url":"https://api.github.com/users/jonathansick","html_url":"https://github.com/jonathansick","followers_url":"https://api.github.com/users/jonathansick/followers","following_url":"https://api.github.com/users/jonathansick/following{/other_user}","gists_url":"https://api.github.com/users/jonathansick/gists{/gist_id}","starred_url":"https://api.github.com/users/jonathansick/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonathansick/subscriptions","organizations_url":"https://api.github.com/users/jonathansick/orgs","repos_url":"https://api.github.com/users/jonathansick/repos","events_url":"https://api.github.com/users/jonathansick/events{/privacy}","received_events_url":"https://api.github.com/users/jonathansick/received_events","type":"User","site_admin":false},"body":"The initial MVP of ltd-keeper had all-or-nothing authentication; any user was effectively an admin user. This adds fine-grained roles that each API user could have (for example, one API user might be able to add a build, but not create an edition or product or add another user).\r\n\r\n- [x] Define a set of permissions\r\n- [x] Add permissions bits to the User model with a helper method to validate that a user has the correct permissions\r\n- [x] Create a decorator that can be added to routes to declare required permissions\r\n- [x] Apply that decorator throughout the codebase\r\n- [x] Test authorization of all secured endpoints\r\n- [x] Add documentation to the individual routes. More general docs may have to wait for a `PUT /users/` route to be added. → [DM-5678](https://jira.lsstcorp.org/browse/DM-5678)\r\n- Deferred: ~~Make it possible for some routes to be semi-opened. For example, a user who administers a Product might see 'private' infrastructural details about the Product that an anonymous user would not see. A way to do this is described in https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/17#issuecomment-76668519~~ → [DM-5679](https://jira.lsstcorp.org/browse/DM-5679)","created_at":"2016-03-30T06:29:56Z","updated_at":"2016-04-06T03:51:32Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6b86a3d7c62ba47c0258176d8859fe58a0b5c559","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3/commits","review_comments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3/comments","review_comment_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/3/comments","statuses_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/statuses/6041f318ab047b7d448f59a6abbd3382b9f19f6e","head":{"label":"lsst-sqre:tickets/DM-5645","ref":"tickets/DM-5645","sha":"6041f318ab047b7d448f59a6abbd3382b9f19f6e","user":{"login":"lsst-sqre","id":10158560,"avatar_url":"https://avatars.githubusercontent.com/u/10158560?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst-sqre","html_url":"https://github.com/lsst-sqre","followers_url":"https://api.github.com/users/lsst-sqre/followers","following_url":"https://api.github.com/users/lsst-sqre/following{/other_user}","gists_url":"https://api.github.com/users/lsst-sqre/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst-sqre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst-sqre/subscriptions","organizations_url":"https://api.github.com/users/lsst-sqre/orgs","repos_url":"https://api.github.com/users/lsst-sqre/repos","events_url":"https://api.github.com/users/lsst-sqre/events{/privacy}","received_events_url":"https://api.github.com/users/lsst-sqre/received_events","type":"Organization","site_admin":false},"repo":{"id":50541238,"name":"ltd-keeper","full_name":"lsst-sqre/ltd-keeper","owner":{"login":"lsst-sqre","id":10158560,"avatar_url":"https://avatars.githubusercontent.com/u/10158560?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst-sqre","html_url":"https://github.com/lsst-sqre","followers_url":"https://api.github.com/users/lsst-sqre/followers","following_url":"https://api.github.com/users/lsst-sqre/following{/other_user}","gists_url":"https://api.github.com/users/lsst-sqre/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst-sqre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst-sqre/subscriptions","organizations_url":"https://api.github.com/users/lsst-sqre/orgs","repos_url":"https://api.github.com/users/lsst-sqre/repos","events_url":"https://api.github.com/users/lsst-sqre/events{/privacy}","received_events_url":"https://api.github.com/users/lsst-sqre/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lsst-sqre/ltd-keeper","description":"LSST the Docs Keeper: RESTful API for managing LSST's published software docs","fork":false,"url":"https://api.github.com/repos/lsst-sqre/ltd-keeper","forks_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/forks","keys_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/teams","hooks_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/hooks","issue_events_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/events{/number}","events_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/events","assignees_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/assignees{/user}","branches_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/branches{/branch}","tags_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/tags","blobs_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/languages","stargazers_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/stargazers","contributors_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/contributors","subscribers_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/subscribers","subscription_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/subscription","commits_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/contents/{+path}","compare_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/merges","archive_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/downloads","issues_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues{/number}","pulls_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/labels{/name}","releases_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/releases{/id}","deployments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/deployments","created_at":"2016-01-27T22:05:25Z","updated_at":"2016-03-09T22:12:51Z","pushed_at":"2016-04-05T17:28:47Z","git_url":"git://github.com/lsst-sqre/ltd-keeper.git","ssh_url":"git@github.com:lsst-sqre/ltd-keeper.git","clone_url":"https://github.com/lsst-sqre/ltd-keeper.git","svn_url":"https://github.com/lsst-sqre/ltd-keeper","homepage":"http://ltd-keeper.lsst.io","size":107,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"lsst-sqre:master","ref":"master","sha":"0b4511f0bfe1be06148a10bc8369335c9d6581ec","user":{"login":"lsst-sqre","id":10158560,"avatar_url":"https://avatars.githubusercontent.com/u/10158560?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst-sqre","html_url":"https://github.com/lsst-sqre","followers_url":"https://api.github.com/users/lsst-sqre/followers","following_url":"https://api.github.com/users/lsst-sqre/following{/other_user}","gists_url":"https://api.github.com/users/lsst-sqre/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst-sqre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst-sqre/subscriptions","organizations_url":"https://api.github.com/users/lsst-sqre/orgs","repos_url":"https://api.github.com/users/lsst-sqre/repos","events_url":"https://api.github.com/users/lsst-sqre/events{/privacy}","received_events_url":"https://api.github.com/users/lsst-sqre/received_events","type":"Organization","site_admin":false},"repo":{"id":50541238,"name":"ltd-keeper","full_name":"lsst-sqre/ltd-keeper","owner":{"login":"lsst-sqre","id":10158560,"avatar_url":"https://avatars.githubusercontent.com/u/10158560?v=3","gravatar_id":"","url":"https://api.github.com/users/lsst-sqre","html_url":"https://github.com/lsst-sqre","followers_url":"https://api.github.com/users/lsst-sqre/followers","following_url":"https://api.github.com/users/lsst-sqre/following{/other_user}","gists_url":"https://api.github.com/users/lsst-sqre/gists{/gist_id}","starred_url":"https://api.github.com/users/lsst-sqre/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lsst-sqre/subscriptions","organizations_url":"https://api.github.com/users/lsst-sqre/orgs","repos_url":"https://api.github.com/users/lsst-sqre/repos","events_url":"https://api.github.com/users/lsst-sqre/events{/privacy}","received_events_url":"https://api.github.com/users/lsst-sqre/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lsst-sqre/ltd-keeper","description":"LSST the Docs Keeper: RESTful API for managing LSST's published software docs","fork":false,"url":"https://api.github.com/repos/lsst-sqre/ltd-keeper","forks_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/forks","keys_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/teams","hooks_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/hooks","issue_events_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/events{/number}","events_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/events","assignees_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/assignees{/user}","branches_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/branches{/branch}","tags_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/tags","blobs_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/refs{/sha}","trees_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/statuses/{sha}","languages_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/languages","stargazers_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/stargazers","contributors_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/contributors","subscribers_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/subscribers","subscription_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/subscription","commits_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/commits{/sha}","git_commits_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/git/commits{/sha}","comments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/comments{/number}","issue_comment_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/comments{/number}","contents_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/contents/{+path}","compare_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/merges","archive_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/downloads","issues_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues{/number}","pulls_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls{/number}","milestones_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/milestones{/number}","notifications_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/labels{/name}","releases_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/releases{/id}","deployments_url":"https://api.github.com/repos/lsst-sqre/ltd-keeper/deployments","created_at":"2016-01-27T22:05:25Z","updated_at":"2016-03-09T22:12:51Z","pushed_at":"2016-04-05T17:28:47Z","git_url":"git://github.com/lsst-sqre/ltd-keeper.git","ssh_url":"git@github.com:lsst-sqre/ltd-keeper.git","clone_url":"https://github.com/lsst-sqre/ltd-keeper.git","svn_url":"https://github.com/lsst-sqre/ltd-keeper","homepage":"http://ltd-keeper.lsst.io","size":107,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3"},"html":{"href":"https://github.com/lsst-sqre/ltd-keeper/pull/3"},"issue":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/3"},"comments":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/lsst-sqre/ltd-keeper/statuses/6041f318ab047b7d448f59a6abbd3382b9f19f6e"}}}}