{"action":"created","comment":{"url":"https://api.github.com/repos/google/oauth2client/pulls/comments/35390993","id":35390993,"diff_hunk":"@@ -0,0 +1,299 @@\n+# Copyright 2015 Google Inc.\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#     http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\n+\"\"\"Utilities for the Flask web framework\n+\"\"\"\n+from __future__ import absolute_import\n+import json\n+import random\n+import string\n+from functools import wraps\n+\n+import httplib2\n+from flask import (Blueprint, current_app, redirect, request, session, url_for,\n+                   _app_ctx_stack)\n+from oauth2client.client import (Credentials, OAuth2WebServerFlow,\n+                                 FlowExchangeError)\n+from oauth2client import clientsecrets\n+\n+\n+DEFAULT_SCOPES = ['email', 'profile']\n+\n+\n+class ConfigurationError(Exception):\n+    \"\"\"Raised when OAuth2 settings are not adequately configured.\"\"\"\n+    pass\n+\n+\n+class NoCredentialsError(Exception):\n+    \"\"\"Raised when attempting to use credentials that do not exist.\"\"\"\n+    pass\n+\n+\n+class OAuth2(object):\n+    \"\"\"Flask extension for making OAuth 2.0 easier.\n+\n+    The extension includes views that handle the entire auth flow and a\n+    @required decorator to automatically ensure that user credentials are\n+    available.\n+\n+    To configure::\n+\n+        app = Flask(__name__)\n+\n+        app.config['SECRET_KEY'] = 'your-secret-key'\n+        app.config['OAUTH2_CLIENT_SECRETS_JSON'] = 'client_secrets.json'\n+\n+        oauth2 = OAuth2(app)\n+\n+\n+    To use::\n+\n+        @app.route('/needs_credentials')\n+        @oauth2.required\n+        def example():\n+            # http is authorized with the user's credentials and can be used\n+            # to make http calls.\n+            http = oauth2.http()\n+\n+            # Or, you can access the credentials directly\n+            credentials = oauth2.credentials\n+\n+\n+        @app.route('/info')\n+        @oauth2.required\n+        def info():\n+            return \"Hello, {}\".format(oauth2.email)\n+\n+    \"\"\"\n+\n+    def __init__(self, app=None, *args, **kwargs):\n+        self.app = app\n+        if app is not None:\n+            self.init_app(app, *args, **kwargs)\n+\n+    def init_app(self, app, scopes=None, client_secrets_file=None,\n+                 client_id=None, client_secret=None, authorize_callback=None,\n+                 logout_callback=None):\n+        self.app = app\n+\n+        if not scopes:\n+            scopes = app.config.get('OAUTH2_SCOPES', DEFAULT_SCOPES)\n+\n+        self.scopes = scopes\n+\n+        self.client_id, self.client_secret = client_id, client_secret\n+\n+        if not self.client_id or not self.client_secret:\n+            if client_secrets_file:\n+                self.client_id, self.client_secret = self._load_client_secrets(\n+                    client_secrets_file)\n+            elif 'OAUTH2_CLIENT_SECRETS_JSON' in app.config:\n+                self.client_id, self.client_secret = self._load_client_secrets(\n+                    app.config['OAUTH2_CLIENT_SECRETS_JSON'])\n+            else:\n+                self.client_id, self.client_secret = self._load_app_config(app)\n+\n+        app.register_blueprint(self._create_blueprint())\n+\n+        self.authorize_callback = authorize_callback\n+        self.logout_callback = logout_callback\n+\n+        app.after_request(self._update_session)\n+\n+    def _load_client_secrets(self, filename):\n+        client_type, client_info = clientsecrets.loadfile(filename)\n+        if client_type != clientsecrets.TYPE_WEB:\n+            raise ConfigurationError(\n+                \"The flow specified in %s isn't supported.\" % client_type)\n+\n+        return client_info['client_id'], client_info['client_secret']\n+\n+    def _load_app_config(self, app):\n+        if ('OAUTH2_CLIENT_SECRET' not in app.config or\n+                'OAUTH2_CLIENT_ID' not in app.config):\n+            raise ConfigurationError(","path":"oauth2client/flask.py","position":126,"original_position":126,"commit_id":"eaeab398abc821ec6ec75b27a4c09bf4d49b612b","original_commit_id":"eaeab398abc821ec6ec75b27a4c09bf4d49b612b","user":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"body":"Ack.","created_at":"2015-07-24T02:16:35Z","updated_at":"2015-07-24T02:16:35Z","html_url":"https://github.com/google/oauth2client/pull/226#discussion_r35390993","pull_request_url":"https://api.github.com/repos/google/oauth2client/pulls/226","_links":{"self":{"href":"https://api.github.com/repos/google/oauth2client/pulls/comments/35390993"},"html":{"href":"https://github.com/google/oauth2client/pull/226#discussion_r35390993"},"pull_request":{"href":"https://api.github.com/repos/google/oauth2client/pulls/226"}}},"pull_request":{"url":"https://api.github.com/repos/google/oauth2client/pulls/226","id":40727012,"html_url":"https://github.com/google/oauth2client/pull/226","diff_url":"https://github.com/google/oauth2client/pull/226.diff","patch_url":"https://github.com/google/oauth2client/pull/226.patch","issue_url":"https://api.github.com/repos/google/oauth2client/issues/226","number":226,"state":"open","locked":false,"title":"Don't Merge; Flask 3LO helper","user":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"body":"This should fix #224 once merged.\r\n\r\nCurrently rough and looking for feedback. This intentionally deviates from the appengine/webapp2 decorator to be more idiomatic for flask users.\r\n\r\n1. The credentials are currently only stored on the session. I'd like some feedback on how to allow the user to specify storage, and how the session storage should fix into that (should it store both in the session and within the user provided storage?)\r\n2. I'm aware that it doesn't quite fit with the style of the rest of the codebase, I'll adjust to 2 spaces etc before merge.\r\n3.  The scopes default to 'email' and 'profile', and there's a mechanism to fetch the user's full profile from google+. I can remove this if we feel it's bloated (users can easily add it back via ``authorize_callback``).\r\n\r\nTests will be added once I validate this approach.","created_at":"2015-07-23T22:48:28Z","updated_at":"2015-07-24T02:16:35Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2f299d245d95c5ad75dec52a09bde3a76e61cdd1","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/google/oauth2client/pulls/226/commits","review_comments_url":"https://api.github.com/repos/google/oauth2client/pulls/226/comments","review_comment_url":"https://api.github.com/repos/google/oauth2client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/google/oauth2client/issues/226/comments","statuses_url":"https://api.github.com/repos/google/oauth2client/statuses/eaeab398abc821ec6ec75b27a4c09bf4d49b612b","head":{"label":"jonparrott:master","ref":"master","sha":"eaeab398abc821ec6ec75b27a4c09bf4d49b612b","user":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"repo":{"id":35853207,"name":"oauth2client","full_name":"jonparrott/oauth2client","owner":{"login":"jonparrott","id":250995,"avatar_url":"https://avatars.githubusercontent.com/u/250995?v=3","gravatar_id":"","url":"https://api.github.com/users/jonparrott","html_url":"https://github.com/jonparrott","followers_url":"https://api.github.com/users/jonparrott/followers","following_url":"https://api.github.com/users/jonparrott/following{/other_user}","gists_url":"https://api.github.com/users/jonparrott/gists{/gist_id}","starred_url":"https://api.github.com/users/jonparrott/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jonparrott/subscriptions","organizations_url":"https://api.github.com/users/jonparrott/orgs","repos_url":"https://api.github.com/users/jonparrott/repos","events_url":"https://api.github.com/users/jonparrott/events{/privacy}","received_events_url":"https://api.github.com/users/jonparrott/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jonparrott/oauth2client","description":"This is a python library for accessing resources protected by OAuth 2.0.","fork":true,"url":"https://api.github.com/repos/jonparrott/oauth2client","forks_url":"https://api.github.com/repos/jonparrott/oauth2client/forks","keys_url":"https://api.github.com/repos/jonparrott/oauth2client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jonparrott/oauth2client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jonparrott/oauth2client/teams","hooks_url":"https://api.github.com/repos/jonparrott/oauth2client/hooks","issue_events_url":"https://api.github.com/repos/jonparrott/oauth2client/issues/events{/number}","events_url":"https://api.github.com/repos/jonparrott/oauth2client/events","assignees_url":"https://api.github.com/repos/jonparrott/oauth2client/assignees{/user}","branches_url":"https://api.github.com/repos/jonparrott/oauth2client/branches{/branch}","tags_url":"https://api.github.com/repos/jonparrott/oauth2client/tags","blobs_url":"https://api.github.com/repos/jonparrott/oauth2client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jonparrott/oauth2client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jonparrott/oauth2client/git/refs{/sha}","trees_url":"https://api.github.com/repos/jonparrott/oauth2client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jonparrott/oauth2client/statuses/{sha}","languages_url":"https://api.github.com/repos/jonparrott/oauth2client/languages","stargazers_url":"https://api.github.com/repos/jonparrott/oauth2client/stargazers","contributors_url":"https://api.github.com/repos/jonparrott/oauth2client/contributors","subscribers_url":"https://api.github.com/repos/jonparrott/oauth2client/subscribers","subscription_url":"https://api.github.com/repos/jonparrott/oauth2client/subscription","commits_url":"https://api.github.com/repos/jonparrott/oauth2client/commits{/sha}","git_commits_url":"https://api.github.com/repos/jonparrott/oauth2client/git/commits{/sha}","comments_url":"https://api.github.com/repos/jonparrott/oauth2client/comments{/number}","issue_comment_url":"https://api.github.com/repos/jonparrott/oauth2client/issues/comments{/number}","contents_url":"https://api.github.com/repos/jonparrott/oauth2client/contents/{+path}","compare_url":"https://api.github.com/repos/jonparrott/oauth2client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jonparrott/oauth2client/merges","archive_url":"https://api.github.com/repos/jonparrott/oauth2client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jonparrott/oauth2client/downloads","issues_url":"https://api.github.com/repos/jonparrott/oauth2client/issues{/number}","pulls_url":"https://api.github.com/repos/jonparrott/oauth2client/pulls{/number}","milestones_url":"https://api.github.com/repos/jonparrott/oauth2client/milestones{/number}","notifications_url":"https://api.github.com/repos/jonparrott/oauth2client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jonparrott/oauth2client/labels{/name}","releases_url":"https://api.github.com/repos/jonparrott/oauth2client/releases{/id}","created_at":"2015-05-19T01:57:46Z","updated_at":"2015-05-19T01:57:47Z","pushed_at":"2015-07-23T22:54:49Z","git_url":"git://github.com/jonparrott/oauth2client.git","ssh_url":"git@github.com:jonparrott/oauth2client.git","clone_url":"https://github.com/jonparrott/oauth2client.git","svn_url":"https://github.com/jonparrott/oauth2client","homepage":"","size":22677,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"c8c89877a29c1a62e24d62e67242e46c9aa1ae68","user":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":14211461,"name":"oauth2client","full_name":"google/oauth2client","owner":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/google/oauth2client","description":"This is a python library for accessing resources protected by OAuth 2.0.","fork":false,"url":"https://api.github.com/repos/google/oauth2client","forks_url":"https://api.github.com/repos/google/oauth2client/forks","keys_url":"https://api.github.com/repos/google/oauth2client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/oauth2client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/oauth2client/teams","hooks_url":"https://api.github.com/repos/google/oauth2client/hooks","issue_events_url":"https://api.github.com/repos/google/oauth2client/issues/events{/number}","events_url":"https://api.github.com/repos/google/oauth2client/events","assignees_url":"https://api.github.com/repos/google/oauth2client/assignees{/user}","branches_url":"https://api.github.com/repos/google/oauth2client/branches{/branch}","tags_url":"https://api.github.com/repos/google/oauth2client/tags","blobs_url":"https://api.github.com/repos/google/oauth2client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/oauth2client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/oauth2client/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/oauth2client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/oauth2client/statuses/{sha}","languages_url":"https://api.github.com/repos/google/oauth2client/languages","stargazers_url":"https://api.github.com/repos/google/oauth2client/stargazers","contributors_url":"https://api.github.com/repos/google/oauth2client/contributors","subscribers_url":"https://api.github.com/repos/google/oauth2client/subscribers","subscription_url":"https://api.github.com/repos/google/oauth2client/subscription","commits_url":"https://api.github.com/repos/google/oauth2client/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/oauth2client/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/oauth2client/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/oauth2client/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/oauth2client/contents/{+path}","compare_url":"https://api.github.com/repos/google/oauth2client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/oauth2client/merges","archive_url":"https://api.github.com/repos/google/oauth2client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/oauth2client/downloads","issues_url":"https://api.github.com/repos/google/oauth2client/issues{/number}","pulls_url":"https://api.github.com/repos/google/oauth2client/pulls{/number}","milestones_url":"https://api.github.com/repos/google/oauth2client/milestones{/number}","notifications_url":"https://api.github.com/repos/google/oauth2client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/oauth2client/labels{/name}","releases_url":"https://api.github.com/repos/google/oauth2client/releases{/id}","created_at":"2013-11-07T18:03:04Z","updated_at":"2015-07-22T18:29:00Z","pushed_at":"2015-07-23T22:54:50Z","git_url":"git://github.com/google/oauth2client.git","ssh_url":"git@github.com:google/oauth2client.git","clone_url":"https://github.com/google/oauth2client.git","svn_url":"https://github.com/google/oauth2client","homepage":"","size":24972,"stargazers_count":209,"watchers_count":209,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":106,"mirror_url":null,"open_issues_count":49,"forks":106,"open_issues":49,"watchers":209,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/oauth2client/pulls/226"},"html":{"href":"https://github.com/google/oauth2client/pull/226"},"issue":{"href":"https://api.github.com/repos/google/oauth2client/issues/226"},"comments":{"href":"https://api.github.com/repos/google/oauth2client/issues/226/comments"},"review_comments":{"href":"https://api.github.com/repos/google/oauth2client/pulls/226/comments"},"review_comment":{"href":"https://api.github.com/repos/google/oauth2client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/google/oauth2client/pulls/226/commits"},"statuses":{"href":"https://api.github.com/repos/google/oauth2client/statuses/eaeab398abc821ec6ec75b27a4c09bf4d49b612b"}}}}