{"action":"created","comment":{"url":"https://api.github.com/repos/google/oauth2client/pulls/comments/35456854","id":35456854,"diff_hunk":"@@ -0,0 +1,329 @@\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+Provides a Flask extension that makes using OAuth2 web server flow easier.\n+The extension includes views that handle the entire auth flow and a @required\n+decorator to automatically ensure that user credentials are available.\n+\n+To configure::\n+\n+    app = Flask(__name__)\n+\n+    app.config['SECRET_KEY'] = 'your-secret-key'\n+\n+    app.config['OAUTH2_CLIENT_SECRETS_JSON'] = 'client_secrets.json'\n+    # or, specify the client id and secret separately\n+    app.config['OAUTH2_CLIENT_ID'] = 'your-client-id'\n+    app.config['OAUTH2_CLIENT_SECRET'] = 'your-client-secret'\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+from __future__ import absolute_import\n+import json\n+import random\n+import string\n+from functools import wraps\n+\n+import httplib2\n+\n+try:\n+    from flask import Blueprint\n+    from flask import _app_ctx_stack\n+    from flask import current_app\n+    from flask import redirect\n+    from flask import request\n+    from flask import session\n+    from flask import url_for\n+except ImportError as e:\n+    print(\"The flask utilities requires flask 0.9 or newer.\")\n+    raise e\n+\n+from oauth2client.client import Credentials\n+from oauth2client.client import FlowExchangeError\n+from oauth2client.client import OAuth2WebServerFlow\n+from oauth2client import clientsecrets\n+\n+\n+DEFAULT_SCOPES = ('email', 'profile')\n+\n+\n+class OAuth2(object):\n+    \"\"\"Flask extension for making OAuth 2.0 easier.\n+\n+    Configuration values:\n+        * OAUTH2_CLIENT_SECRETS_JSON path to a client secrets json file,\n+            obtained from the credentials screen in the Google Developers\n+            console.\n+        * OAUTH2_CLIENT_ID the oauth2 credentials' client ID. This is only\n+            needed if OAUTH2_CLIENT_SECRETS_JSON is not specified.\n+        * OAUTH2_CLIENT_SECRET the oauth2 credentials' client secret. This is\n+            only needed if OAUTH2_CLIENT_SECRETS_JSON is not specified.\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 ValueError(\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 ValueError(\n+                \"OAuth2 configuration could not be found. Either \"\n+                \"OAUTH2_CLIENT_SECRETS_JSON or OAUTH2_CLIENT_ID and \"\n+                \"OAUTH2_CLIENT_SECRET must be specified.\")\n+\n+        return (app.config['OAUTH2_CLIENT_ID'],\n+                app.config['OAUTH2_CLIENT_SECRET'])\n+\n+    def _update_session(self, resp):\n+        \"\"\"Ensures that the session's copy of the credentials is updated\n+        when the credential refreshes its access token.\"\"\"\n+        ctx = _app_ctx_stack.top\n+        if hasattr(ctx, 'oauth2_credentials'):\n+            session['oauth2credentials'] = ctx.oauth2_credentials.to_json()\n+        return resp\n+\n+    def _request_user_info(self):\n+        \"\"\"Gets the user's profile information from Google+","path":"oauth2client/flask.py","position":157,"original_position":157,"commit_id":"5d3ca8dc85ba70f8fae41682895a7625a5e71970","original_commit_id":"5d3ca8dc85ba70f8fae41682895a7625a5e71970","user":{"login":"anthmgoogle","id":6653712,"avatar_url":"https://avatars.githubusercontent.com/u/6653712?v=3","gravatar_id":"","url":"https://api.github.com/users/anthmgoogle","html_url":"https://github.com/anthmgoogle","followers_url":"https://api.github.com/users/anthmgoogle/followers","following_url":"https://api.github.com/users/anthmgoogle/following{/other_user}","gists_url":"https://api.github.com/users/anthmgoogle/gists{/gist_id}","starred_url":"https://api.github.com/users/anthmgoogle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anthmgoogle/subscriptions","organizations_url":"https://api.github.com/users/anthmgoogle/orgs","repos_url":"https://api.github.com/users/anthmgoogle/repos","events_url":"https://api.github.com/users/anthmgoogle/events{/privacy}","received_events_url":"https://api.github.com/users/anthmgoogle/received_events","type":"User","site_admin":false},"body":"As discussed, this might be higher level than we want to go in oauth2client. There is a more comprehensive Google Signin library that should cover this sort of case.","created_at":"2015-07-24T19:18:18Z","updated_at":"2015-07-24T19:18:18Z","html_url":"https://github.com/google/oauth2client/pull/226#discussion_r35456854","pull_request_url":"https://api.github.com/repos/google/oauth2client/pulls/226","_links":{"self":{"href":"https://api.github.com/repos/google/oauth2client/pulls/comments/35456854"},"html":{"href":"https://github.com/google/oauth2client/pull/226#discussion_r35456854"},"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-24T19:18:18Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4ab2428b630df7253d5cd2a9024c8f6b90dfec19","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/5d3ca8dc85ba70f8fae41682895a7625a5e71970","head":{"label":"jonparrott:master","ref":"master","sha":"5d3ca8dc85ba70f8fae41682895a7625a5e71970","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-24T02:39: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-24T15:24:37Z","pushed_at":"2015-07-24T02:39: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":208,"watchers_count":208,"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":208,"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/5d3ca8dc85ba70f8fae41682895a7625a5e71970"}}}}