{"action":"created","comment":{"url":"https://api.github.com/repos/juju/charmstore/pulls/comments/61104054","id":61104054,"diff_hunk":"@@ -22,163 +22,284 @@ import (\n \t\"gopkg.in/juju/charmstore.v5-unstable/internal/router\"\n )\n \n+const UsernameAttr = \"username\"\n+\n+// authorization conatains authorization information extracted from an HTTP request.\n+// The zero value for a authorization contains no privileges.\n+type authorization struct {\n+\tAdmin    bool\n+\tUsername string\n+}\n+\n const (\n \tPromulgatorsGroup = \"charmers\"\n \n-\t// OpAccessCharmWithTerms indicates an operation of accessing the archive of\n-\t// a charm that requires agreement to certain terms and conditions.\n-\tOpAccessCharmWithTerms = \"op-get-with-terms\"\n-\n-\t// OpOther indicates all other operations.\n-\t// This operation should not be added as part of a macaroon caveat.\n-\tOpOther = \"op-other\"\n+\tdefaultMacaroonExpiry   = 24 * time.Hour\n+\tshortTermMacaroonExpiry = time.Minute\n+)\n \n-\tdefaultMacaroonExpiry = 24 * time.Hour\n+// These operations represent categories of operation by the user on\n+// the charm store. All the operations should be\n+// mutually distinct.\n+const (\n+\t// OpReadWithNoTerms is the operation of reading an entity\n+\t// where either the entity has no associated terms and conditions\n+\t// or we don't need to agree to them to access the entity.\n+\tOpReadWithNoTerms = \"read-no-terms\"\n+\n+\t// OpReadWithTerms is the operation of reading an entity\n+\t// where terms must be agreed to before contents\n+\t// are provided.\n+\tOpReadWithTerms = \"read-with-terms\"\n+\n+\t// OpWrite indicates an operation that changes something in the charmstore.\n+\tOpWrite = \"write\"\n )\n \n+// authnCheckableOps holds the set of operations that\n+// can be authorized with just a username.\n+var authnCheckableOps = []string{\n+\tOpReadWithNoTerms,\n+\tOpWrite,\n+}\n+\n // timeNow is defined as a variable so that it can be overridden in tests.\n var timeNow = time.Now\n \n-// authorize checks that the current user is authorized based on the provided\n-// ACL and optional entity. If an authenticated user is required, authorize tries to retrieve the\n-// current user in the following ways:\n-// - by checking that the request's headers HTTP basic auth credentials match\n-//   the superuser credentials stored in the API handler;\n+// AuthorizeEntity is a convenience method that calls authorize to check\n+// that the given request can access the entity with the given id to\n+// perform all of the given operations. The operation will be chosen based\n+// on the request method (OpReadNonArchive for non-mutating HTTP\n+// methods; OpWrite for others).\n+//\n+// If the request fails because authorization is denied, a macaroon is\n+// minted that will provide access when discharged, and returned as a\n+// discharge-required error.\n+//\n+// This method implements router.Context.AuthorizeEntity.\n+func (h *ReqHandler) AuthorizeEntity(id *router.ResolvedURL, req *http.Request) error {\n+\tvar op string\n+\tswitch req.Method {\n+\tcase \"GET\", \"HEAD\", \"OPTIONS\":\n+\t\top = OpReadWithNoTerms\n+\tdefault:\n+\t\top = OpWrite\n+\t}\n+\treturn h.AuthorizeEntityForOp(id, req, op)\n+}\n+\n+// Authenticate is a convenience method that calls authorize to check\n+// that that the given request is authenticated for some user.\n+func (h *ReqHandler) Authenticate(req *http.Request) (authorization, error) {\n+\treturn h.authorize(authorizeParams{\n+\t\treq: req,\n+\t\tacls: []mongodoc.ACL{{\n+\t\t\tRead: []string{params.Everyone},\n+\t\t}},\n+\t\tops:           []string{OpReadWithNoTerms},\n+\t\tauthnRequired: true,\n+\t})\n+}\n+\n+func (h *ReqHandler) AuthorizeEntityForOp(id *router.ResolvedURL, req *http.Request, op string) error {","path":"internal/v5/auth.go","position":108,"original_position":108,"commit_id":"97038efebccc7474ff153318a2d56f9844f0da43","original_commit_id":"a972a0624a21567f76a55bf2c6d3697d94c931ba","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"body":"done","created_at":"2016-04-26T15:11:23Z","updated_at":"2016-04-26T15:11:23Z","html_url":"https://github.com/juju/charmstore/pull/609#discussion_r61104054","pull_request_url":"https://api.github.com/repos/juju/charmstore/pulls/609","_links":{"self":{"href":"https://api.github.com/repos/juju/charmstore/pulls/comments/61104054"},"html":{"href":"https://github.com/juju/charmstore/pull/609#discussion_r61104054"},"pull_request":{"href":"https://api.github.com/repos/juju/charmstore/pulls/609"}}},"pull_request":{"url":"https://api.github.com/repos/juju/charmstore/pulls/609","id":67844549,"html_url":"https://github.com/juju/charmstore/pull/609","diff_url":"https://github.com/juju/charmstore/pull/609.diff","patch_url":"https://github.com/juju/charmstore/pull/609.patch","issue_url":"https://api.github.com/repos/juju/charmstore/issues/609","number":609,"state":"open","locked":false,"title":"internal/v5: refactor authorization code","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"body":"We now have a single entry point for all authorization, rather\r\nthan using two somewhat similar parallel authorization paths.\r\n\r\nWe change the operations so that we no longer use the\r\npotentially fragile Deny caveat, instead issuing macaroons\r\nthat explicitly state the set of allowed operations.\r\n\r\nPlus we clean up the code in several places, including using\r\nthe caching idm permission checker, so we won't need to\r\nbe round-tripping to idm so often.","created_at":"2016-04-26T08:23:13Z","updated_at":"2016-04-26T15:11:23Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6d0376bdfe6b2b4e86bb50987ad17fadaee77818","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/juju/charmstore/pulls/609/commits","review_comments_url":"https://api.github.com/repos/juju/charmstore/pulls/609/comments","review_comment_url":"https://api.github.com/repos/juju/charmstore/pulls/comments{/number}","comments_url":"https://api.github.com/repos/juju/charmstore/issues/609/comments","statuses_url":"https://api.github.com/repos/juju/charmstore/statuses/97038efebccc7474ff153318a2d56f9844f0da43","head":{"label":"rogpeppe:210-one-path-auth","ref":"210-one-path-auth","sha":"97038efebccc7474ff153318a2d56f9844f0da43","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"repo":{"id":21278168,"name":"charmstore","full_name":"rogpeppe/charmstore","owner":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rogpeppe/charmstore","description":"The charm store server.","fork":true,"url":"https://api.github.com/repos/rogpeppe/charmstore","forks_url":"https://api.github.com/repos/rogpeppe/charmstore/forks","keys_url":"https://api.github.com/repos/rogpeppe/charmstore/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rogpeppe/charmstore/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rogpeppe/charmstore/teams","hooks_url":"https://api.github.com/repos/rogpeppe/charmstore/hooks","issue_events_url":"https://api.github.com/repos/rogpeppe/charmstore/issues/events{/number}","events_url":"https://api.github.com/repos/rogpeppe/charmstore/events","assignees_url":"https://api.github.com/repos/rogpeppe/charmstore/assignees{/user}","branches_url":"https://api.github.com/repos/rogpeppe/charmstore/branches{/branch}","tags_url":"https://api.github.com/repos/rogpeppe/charmstore/tags","blobs_url":"https://api.github.com/repos/rogpeppe/charmstore/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rogpeppe/charmstore/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rogpeppe/charmstore/git/refs{/sha}","trees_url":"https://api.github.com/repos/rogpeppe/charmstore/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rogpeppe/charmstore/statuses/{sha}","languages_url":"https://api.github.com/repos/rogpeppe/charmstore/languages","stargazers_url":"https://api.github.com/repos/rogpeppe/charmstore/stargazers","contributors_url":"https://api.github.com/repos/rogpeppe/charmstore/contributors","subscribers_url":"https://api.github.com/repos/rogpeppe/charmstore/subscribers","subscription_url":"https://api.github.com/repos/rogpeppe/charmstore/subscription","commits_url":"https://api.github.com/repos/rogpeppe/charmstore/commits{/sha}","git_commits_url":"https://api.github.com/repos/rogpeppe/charmstore/git/commits{/sha}","comments_url":"https://api.github.com/repos/rogpeppe/charmstore/comments{/number}","issue_comment_url":"https://api.github.com/repos/rogpeppe/charmstore/issues/comments{/number}","contents_url":"https://api.github.com/repos/rogpeppe/charmstore/contents/{+path}","compare_url":"https://api.github.com/repos/rogpeppe/charmstore/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rogpeppe/charmstore/merges","archive_url":"https://api.github.com/repos/rogpeppe/charmstore/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rogpeppe/charmstore/downloads","issues_url":"https://api.github.com/repos/rogpeppe/charmstore/issues{/number}","pulls_url":"https://api.github.com/repos/rogpeppe/charmstore/pulls{/number}","milestones_url":"https://api.github.com/repos/rogpeppe/charmstore/milestones{/number}","notifications_url":"https://api.github.com/repos/rogpeppe/charmstore/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rogpeppe/charmstore/labels{/name}","releases_url":"https://api.github.com/repos/rogpeppe/charmstore/releases{/id}","deployments_url":"https://api.github.com/repos/rogpeppe/charmstore/deployments","created_at":"2014-06-27T14:18:37Z","updated_at":"2014-06-27T13:23:16Z","pushed_at":"2016-04-26T09:44:06Z","git_url":"git://github.com/rogpeppe/charmstore.git","ssh_url":"git@github.com:rogpeppe/charmstore.git","clone_url":"https://github.com/rogpeppe/charmstore.git","svn_url":"https://github.com/rogpeppe/charmstore","homepage":null,"size":4615,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"juju:v5-unstable","ref":"v5-unstable","sha":"6802ac223fbffd5915f4255b2b008b9b8db8268f","user":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"repo":{"id":20720417,"name":"charmstore","full_name":"juju/charmstore","owner":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/juju/charmstore","description":"The charm store server.","fork":false,"url":"https://api.github.com/repos/juju/charmstore","forks_url":"https://api.github.com/repos/juju/charmstore/forks","keys_url":"https://api.github.com/repos/juju/charmstore/keys{/key_id}","collaborators_url":"https://api.github.com/repos/juju/charmstore/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/juju/charmstore/teams","hooks_url":"https://api.github.com/repos/juju/charmstore/hooks","issue_events_url":"https://api.github.com/repos/juju/charmstore/issues/events{/number}","events_url":"https://api.github.com/repos/juju/charmstore/events","assignees_url":"https://api.github.com/repos/juju/charmstore/assignees{/user}","branches_url":"https://api.github.com/repos/juju/charmstore/branches{/branch}","tags_url":"https://api.github.com/repos/juju/charmstore/tags","blobs_url":"https://api.github.com/repos/juju/charmstore/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/juju/charmstore/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/juju/charmstore/git/refs{/sha}","trees_url":"https://api.github.com/repos/juju/charmstore/git/trees{/sha}","statuses_url":"https://api.github.com/repos/juju/charmstore/statuses/{sha}","languages_url":"https://api.github.com/repos/juju/charmstore/languages","stargazers_url":"https://api.github.com/repos/juju/charmstore/stargazers","contributors_url":"https://api.github.com/repos/juju/charmstore/contributors","subscribers_url":"https://api.github.com/repos/juju/charmstore/subscribers","subscription_url":"https://api.github.com/repos/juju/charmstore/subscription","commits_url":"https://api.github.com/repos/juju/charmstore/commits{/sha}","git_commits_url":"https://api.github.com/repos/juju/charmstore/git/commits{/sha}","comments_url":"https://api.github.com/repos/juju/charmstore/comments{/number}","issue_comment_url":"https://api.github.com/repos/juju/charmstore/issues/comments{/number}","contents_url":"https://api.github.com/repos/juju/charmstore/contents/{+path}","compare_url":"https://api.github.com/repos/juju/charmstore/compare/{base}...{head}","merges_url":"https://api.github.com/repos/juju/charmstore/merges","archive_url":"https://api.github.com/repos/juju/charmstore/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/juju/charmstore/downloads","issues_url":"https://api.github.com/repos/juju/charmstore/issues{/number}","pulls_url":"https://api.github.com/repos/juju/charmstore/pulls{/number}","milestones_url":"https://api.github.com/repos/juju/charmstore/milestones{/number}","notifications_url":"https://api.github.com/repos/juju/charmstore/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/juju/charmstore/labels{/name}","releases_url":"https://api.github.com/repos/juju/charmstore/releases{/id}","deployments_url":"https://api.github.com/repos/juju/charmstore/deployments","created_at":"2014-06-11T09:53:37Z","updated_at":"2016-04-17T02:04:55Z","pushed_at":"2016-04-26T09:44:06Z","git_url":"git://github.com/juju/charmstore.git","ssh_url":"git@github.com:juju/charmstore.git","clone_url":"https://github.com/juju/charmstore.git","svn_url":"https://github.com/juju/charmstore","homepage":null,"size":6289,"stargazers_count":8,"watchers_count":8,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":21,"mirror_url":null,"open_issues_count":26,"forks":21,"open_issues":26,"watchers":8,"default_branch":"v5-unstable"}},"_links":{"self":{"href":"https://api.github.com/repos/juju/charmstore/pulls/609"},"html":{"href":"https://github.com/juju/charmstore/pull/609"},"issue":{"href":"https://api.github.com/repos/juju/charmstore/issues/609"},"comments":{"href":"https://api.github.com/repos/juju/charmstore/issues/609/comments"},"review_comments":{"href":"https://api.github.com/repos/juju/charmstore/pulls/609/comments"},"review_comment":{"href":"https://api.github.com/repos/juju/charmstore/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/juju/charmstore/pulls/609/commits"},"statuses":{"href":"https://api.github.com/repos/juju/charmstore/statuses/97038efebccc7474ff153318a2d56f9844f0da43"}}}}