{"action":"created","comment":{"url":"https://api.github.com/repos/google/oauth2client/pulls/comments/35816147","id":35816147,"diff_hunk":"@@ -0,0 +1,403 @@\n+# Copyright 2015 Google Inc.  All rights reserved.\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+    # Note that app.route should be the outermost decorator.\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+    @app.route('/optional')\n+    def optional():\n+        if oauth2.has_credentials():\n+            return 'Credentials found!'\n+        else:\n+            return 'No credentials!'\n+\n+\"\"\"\n+\n+__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'\n+\n+import random\n+import string\n+from functools import wraps\n+\n+import six.moves.http_client as httplib\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:\n+    raise ImportError('The flask utilities require flask 0.9 or newer.')\n+\n+from oauth2client.client import FlowExchangeError\n+from oauth2client.client import OAuth2Credentials\n+from oauth2client.client import OAuth2WebServerFlow\n+from oauth2client.client import Storage\n+from oauth2client import clientsecrets\n+\n+\n+DEFAULT_SCOPES = ('email',)\n+\n+\n+class OAuth2(object):\n+    \"\"\"Flask extension for making OAuth 2.0 easier.\n+\n+    Configuration values:\n+        * GOOGLE_OAUTH2_CLIENT_SECRETS_JSON path to a client secrets json file,\n+            obtained from the credentials screen in the Google Developers\n+            console.\n+        * GOOGLE_OAUTH2_CLIENT_ID the oauth2 credentials' client ID. This is\n+            only needed if OAUTH2_CLIENT_SECRETS_JSON is not specified.\n+        * GOOGLE_OAUTH2_CLIENT_SECRET the oauth2 credentials' client secret.\n+            This is only needed if OAUTH2_CLIENT_SECRETS_JSON is not specified.\n+\n+    If app is specified, all arguments will be passed along to init_app.\n+\n+    If no app is specified, then you should call init_app in your application\n+    factory to finish initialization.\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, storage=None, **kwargs):\n+        \"\"\"Initialize this extension for the given app.\n+\n+        Arguments:\n+            app: A Flask application.\n+            scopes: Optional list of scopes to authorize.\n+            client_secrets_file: Path to a file containing client secrets. You\n+                can also specify the OAUTH2_CLIENT_SECRETS_JSON config value.\n+            client_id: If not specifying a client secrets file, specify the\n+                OAuth2 client id. You can also specify the\n+                GOOGLE_OAUTH2_CLIENT_ID config value. You must also provide a\n+                client secret.\n+            client_secret: The OAuth2 client secret. You can also specify the\n+                GOOGLE_OAUTH2_CLIENT_SECRET config value.\n+            authorize_callback: A function that is executed after successful\n+                user authorization.\n+            logout_callback: A function that is executed after the user is\n+                directed to /oauth2logout\n+            storage: A oauth2client.client.Storage subclass for storing the\n+                credentials. By default, this is a Flask session based storage.\n+            kwargs: Any additional args are passed along to the Flow\n+                constructor.\n+        \"\"\"\n+        self.app = app\n+        self.authorize_callback = authorize_callback\n+        self.logout_callback = logout_callback\n+        self.flow_kwargs = kwargs\n+\n+        if storage is None:\n+            storage = FlaskSessionStorage()\n+        self.storage = storage\n+\n+        if scopes is None:\n+            scopes = app.config.get('GOOGLE_OAUTH2_SCOPES', DEFAULT_SCOPES)\n+        self.scopes = scopes\n+\n+        self._load_config(client_secrets_file, client_id, client_secret)\n+\n+        app.register_blueprint(self._create_blueprint())\n+\n+        #app.after_request(self._update_session)\n+\n+    def _load_config(self, client_secrets_file, client_id, client_secret):\n+        \"\"\"Loads oauth2 configuration in order of priority.\n+\n+        Priority:\n+            1. Config passed to the constructor or init_app.\n+            2. Config passed via the GOOGLE_OAUTH2_CLIENT_SECRETS_FILE app\n+               config.\n+            3. Config passed via the GOOGLE_OAUTH2_CLIENT_ID and\n+               GOOGLE_OAUTH2_CLIENT_SECRET app config.\n+\n+        Raises:\n+            ValueError if no config could be found.\n+        \"\"\"\n+        if client_id and client_secret:\n+            self.client_id, self.client_secret = client_id, client_secret\n+            return\n+\n+        if client_secrets_file:\n+            self._load_client_secrets(client_secrets_file)\n+            return\n+\n+        if 'GOOGLE_OAUTH2_CLIENT_SECRETS_FILE' in self.app.config:\n+            self._load_client_secrets(\n+                self.app.config['GOOGLE_OAUTH2_CLIENT_SECRETS_FILE'])\n+            return\n+\n+        try:\n+            self.client_id, self.client_secret = (\n+                self.app.config['GOOGLE_OAUTH2_CLIENT_ID'],\n+                self.app.config['GOOGLE_OAUTH2_CLIENT_SECRET'])\n+        except KeyError:\n+            raise ValueError(\n+                'OAuth2 configuration could not be found. Either specify the '\n+                'client_secrets_file or client_id and client_secret or set the'\n+                'app configuration variables GOOGLE_OAUTH2_CLIENT_SECRETS_FILE '\n+                'or GOOGLE_OAUTH2_CLIENT_ID and GOOGLE_OAUTH2_CLIENT_SECRET.')\n+\n+    def _load_client_secrets(self, filename):\n+        \"\"\"Loads client secrets from the given 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 is not supported.' % client_type)\n+\n+        self.client_id = client_info['client_id']\n+        self.client_secret = client_info['client_secret']\n+\n+    def _make_flow(self, **kwargs):\n+        \"\"\"Creates a Web Server Flow\"\"\"\n+        # Generate a CSRF token to prevent malicious requests.\n+        state = ''.join(random.choice(string.ascii_uppercase + string.digits)\n+                        for _ in range(32))\n+\n+        session['google_oauth2_state'] = state\n+\n+        kw = self.flow_kwargs.copy()\n+        kw.update(kwargs)\n+\n+        return OAuth2WebServerFlow(\n+            client_id=self.client_id,\n+            client_secret=self.client_secret,\n+            scope=list(self.scopes) + kw.pop('scopes', []),\n+            state=state,\n+            redirect_uri=url_for('oauth2.callback', _external=True),\n+            **kw)\n+\n+    def _create_blueprint(self):\n+        bp = Blueprint('oauth2', __name__)\n+        bp.add_url_rule('/oauth2authorize', 'authorize', self.authorize_view)\n+        bp.add_url_rule('/oauth2logout', 'logout', self.logout_view)\n+        bp.add_url_rule('/oauth2callback', 'callback', self.callback_view)\n+\n+        return bp\n+\n+    def authorize_view(self):\n+        args = request.args.to_dict()\n+\n+        return_url = args.pop('return_url', None)\n+        if return_url is None:\n+            return_url = request.referrer or '/'\n+        session['google_oauth2_return'] = return_url\n+\n+        flow = self._make_flow(**args)\n+        auth_url = flow.step1_get_authorize_url()\n+\n+        return redirect(auth_url)\n+\n+    def logout_view(self):\n+        self.storage.delete()\n+\n+        if self.logout_callback:\n+            self.logout_callback()\n+\n+        return redirect(request.referrer or '/')\n+\n+    def callback_view(self):\n+        if 'error' in request.args:\n+            reason = request.args.get(\n+                'error_description', request.args.get('error', ''))\n+            return 'Authorization failed: %s' % reason, httplib.BAD_REQUEST\n+\n+        try:\n+            client_csrf = request.args['state']\n+            server_csrf = session['google_oauth2_state']\n+            code = request.args['code']\n+        except KeyError:\n+            return 'Invalid request', httplib.BAD_REQUEST\n+\n+        if client_csrf != server_csrf:\n+            return 'Invalid request state', httplib.BAD_REQUEST\n+\n+        flow = self._make_flow()\n+\n+        # Exchange the auth code for credentials.\n+        try:\n+            credentials = flow.step2_exchange(code)\n+        except FlowExchangeError as exchange_error:\n+            current_app.logger.exception(exchange_error)\n+            return 'An error occurred: %s' % exchange_error, httplib.BAD_REQUEST\n+\n+        # Save the credentials to the storage.\n+        self.storage.put(credentials)\n+\n+        if self.authorize_callback:\n+            self.authorize_callback(credentials)\n+\n+        return redirect(session.pop('google_oauth2_return', '/'))\n+\n+    @property\n+    def credentials(self):\n+        ctx = _app_ctx_stack.top\n+\n+        if not hasattr(ctx, 'google_oauth2_credentials'):\n+            ctx.google_oauth2_credentials = self.storage.get()\n+\n+        return ctx.google_oauth2_credentials\n+\n+    def has_credentials(self):\n+        return self.credentials and not self.credentials.invalid\n+\n+    @property\n+    def email(self):\n+        \"\"\"Returns the user's email address.\n+\n+        The email address is provided by the current credentials' id_token. The\n+        use can change their email, it's more appropriate to use user_id for\n+        a unique, user-specific identifier.\n+        \"\"\"\n+        if not self.credentials:\n+            return None\n+        try:\n+            return self.credentials.id_token['email']\n+        except KeyError:\n+            current_app.logger.error(\n+                'Invalid id_token %s', self.credentials.id_token)\n+\n+    @property\n+    def user_id(self):\n+        \"\"\"Returns the user's email address.","path":"oauth2client/flask_util.py","position":318,"original_position":318,"commit_id":"204850f612ac5a812af7c2e496445742bbd7ad0e","original_commit_id":"204850f612ac5a812af7c2e496445742bbd7ad0e","user":{"login":"nathanielmanistaatgoogle","id":10131044,"avatar_url":"https://avatars.githubusercontent.com/u/10131044?v=3","gravatar_id":"","url":"https://api.github.com/users/nathanielmanistaatgoogle","html_url":"https://github.com/nathanielmanistaatgoogle","followers_url":"https://api.github.com/users/nathanielmanistaatgoogle/followers","following_url":"https://api.github.com/users/nathanielmanistaatgoogle/following{/other_user}","gists_url":"https://api.github.com/users/nathanielmanistaatgoogle/gists{/gist_id}","starred_url":"https://api.github.com/users/nathanielmanistaatgoogle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nathanielmanistaatgoogle/subscriptions","organizations_url":"https://api.github.com/users/nathanielmanistaatgoogle/orgs","repos_url":"https://api.github.com/users/nathanielmanistaatgoogle/repos","events_url":"https://api.github.com/users/nathanielmanistaatgoogle/events{/privacy}","received_events_url":"https://api.github.com/users/nathanielmanistaatgoogle/received_events","type":"User","site_admin":false},"body":"I see a code path along which this property may be None - please document in what circumstances the property is None.","created_at":"2015-07-29T21:47:03Z","updated_at":"2015-07-29T21:47:03Z","html_url":"https://github.com/google/oauth2client/pull/226#discussion_r35816147","pull_request_url":"https://api.github.com/repos/google/oauth2client/pulls/226","_links":{"self":{"href":"https://api.github.com/repos/google/oauth2client/pulls/comments/35816147"},"html":{"href":"https://github.com/google/oauth2client/pull/226#discussion_r35816147"},"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":"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-29T21:47:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"21e09a97a14ddab7252b90358f17f321dd595d19","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/204850f612ac5a812af7c2e496445742bbd7ad0e","head":{"label":"jonparrott:master","ref":"master","sha":"204850f612ac5a812af7c2e496445742bbd7ad0e","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-29T21:24:10Z","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-28T05:56:26Z","pushed_at":"2015-07-29T21:24:10Z","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":210,"watchers_count":210,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":108,"mirror_url":null,"open_issues_count":49,"forks":108,"open_issues":49,"watchers":210,"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/204850f612ac5a812af7c2e496445742bbd7ad0e"}}}}