首页 > 解决方案 > Flask Google Login with Google Drive access

问题描述

I'm trying to create a Flask app that can browse to the user's Google Drive to select a file to convert to CSV format.

I found online a code to add Google Login (see below), but I don't understand how to print all files from the logged user. I figured out I need to add https://www.googleapis.com/auth/drive.file to the scope but I can't understand how to list all files from an authorized user (current_user.paying==True) in index. I know that this could be done with the following code, but I'm not sure how to defined the credentials.

In callback an access token is already generated, so I tried to access the Google Drive API to just search for the user's Google sheets:

from httplib2 import Http
from apiclient import discovery
from oauth2client import file

with open("./credentials.json", 'w') as outfile:
    json.dump(token_response.json(), outfile)

store = file.Storage("./credentials.json")
credentials = store.get()
drive = discovery.build("drive", "v3", http=credentials.authorize(Http()))
files = drive.files().list(q="mimeType='application/vnd.google-apps.spreadsheet'").execute()

But this gives me an "KeyError: '_module'" error that's not very informative.

This is the entire Flask code with Google Login

import os
import requests
import json
from httplib2 import Http
from apiclient import discovery
from oauthlib.oauth2 import WebApplicationClient

from flask import Flask, redirect, request, url_for
from flask_login import LoginManager, current_user, login_required, login_user, logout_user
from flask_login import UserMixin

authorized_users = ["myuser@gmail.com"]


class User(UserMixin):
    def __init__(self, id_, paying):
        self.id = id_
        self.paying = paying

    @staticmethod
    def get(user_email):

        if user_email not in authorized_users:
            user = User(user_email, False)
        else:
            user = User(user_email, True)
        return user



# Configuration
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", None)
GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", None)
GOOGLE_DISCOVERY_URL = ("https://accounts.google.com/.well-known/openid-configuration")

# Flask app setup
app = Flask(__name__)
app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24)

# User session management setup
login_manager = LoginManager()
login_manager.init_app(app)

# OAuth 2 client setup
client = WebApplicationClient(GOOGLE_CLIENT_ID)


# Flask-Login helper to retrieve a user from our db
@login_manager.user_loader
def load_user(user_email):
    return User.get(user_email)


def get_google_provider_cfg():
    return requests.get(GOOGLE_DISCOVERY_URL).json()


google_provider_cfg = get_google_provider_cfg()
token_endpoint = google_provider_cfg["token_endpoint"]

print("Google provider cfg", google_provider_cfg)

ACCESS_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'


@app.route("/")
def index():
    if current_user.is_authenticated:
        if current_user.paying:
            return f"<p>Hello, {current_user.id}! You're logged in!</p><br/><a class='button' href='/logout'>Logout</a>"
        else:
            return "Not authorized!<br/><a class='button' href='/logout'>Logout</a>"
    else:
        return f"<a class='button' href='/login'>Google Login</a>"


@app.route("/login")
def login():
    # Find out what URL to hit for Google login
    google_provider_cfg = get_google_provider_cfg()
    authorization_endpoint = google_provider_cfg["authorization_endpoint"]

    # Use library to construct the request for Google login and provide
    # scopes that let you retrieve user's profile from Google
    request_uri = client.prepare_request_uri(
        authorization_endpoint,
        redirect_uri=request.base_url + "/callback",
        scope=["openid", "email", "profile", "https://www.googleapis.com/auth/drive.file"],
    )
    return redirect(request_uri)


@app.route("/login/callback")
def callback():
    # Get authorization code Google sent back to you
    code = request.args.get("code")
    # Prepare and send a request to get tokens! Yay tokens!
    token_url, headers, body = client.prepare_token_request(
        token_endpoint,
        authorization_response=request.url,
        redirect_url=request.base_url,
        code=code
    )
    token_response = requests.post(
        token_url,
        headers=headers,
        data=body,
        auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET),
    )

    # Parse the tokens!
    client.parse_request_body_response(json.dumps(token_response.json()))

    # Now that you have tokens (yay) let's find and hit the URL
    # from Google that gives you the user's profile information,
    # including their Google profile image and email
    userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
    uri, headers, body = client.add_token(userinfo_endpoint)
    userinfo_response = requests.get(uri, headers=headers, data=body)
    if userinfo_response.json().get("email_verified"):
        # unique_id = userinfo_response.json()["sub"]
        users_email = userinfo_response.json()["email"]
        picture = userinfo_response.json()["picture"]
        users_name = userinfo_response.json()["given_name"]
    else:
        return "User email not available or not verified by Google.", 400

    # Create a user in your db with the information provided
    # by Google
    user = User(users_email, False)
    #
    # # Doesn't exist? Add it to the database.
    # if not User.get(unique_id):
    # User.create(unique_id, users_name, users_email, picture)

    print("Logging", users_name, users_email, picture)

    # Begin user session by logging the user in
    login_user(user)

    # Send user back to homepage
    return redirect(url_for("index"))


@app.route("/logout")
@login_required
def logout():
    logout_user()
    print("Logging out")
    return redirect(url_for("index"))


if __name__ == "__main__":
    app.run(ssl_context="adhoc", debug=True)

标签: flaskgoogle-drive-api

解决方案


获取凭据的一种简单方法是

  • 访问Google Drive API 快速入门
  • 点击Enable Drive
  • 记下Client IdandClient Secret或单击DOWNLOAD CLIENT CONFIGURATION并打开生成和下载的credentials.json文件

或者,您可以

更新

KeyError: '_module'是由于检索json结构不正确的文件而导致的错误。

可能的原因:

  • 使用需要client_secrets.json文件的 API 客户端库 - 这与credentials.json您从 Google Drive API 快速入门获得的文件不同
  • json在文件中存储无效凭据
  • 将文件存储json在 py 文件之外的另一个文件夹中
  • 使用无效范围

我了解您使用WebApplicationClient并需要为 Web Server 创建相应的客户端凭据

json file应该有类型的内容

{"web":{"client_id":"XXXX.apps.googleusercontent.com","project_id":"XXXX","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"XXX","redirect_uris":["https://script.google.com/oauthcallback"]}}

还:

范围https://www.googleapis.com/auth/drive.file不足以列出用户驱动器上的所有文件 - 它仅允许您访问使用您的应用程序创建/打开的文件。更多信息在这里

我建议您出于调试目的将您的应用程序置于第一个大范围内。一旦您解决了凭据问题并且您的应用程序可以正常工作 - 看看您可以在不影响应用程序功能的情况下限制范围多远。


推荐阅读