3
votes

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 = ["[email protected]"]


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)
1

1 Answers

1
votes

An easy way to obtain the credentials is

  • Visit the Google Drive API Quickstart
  • Click on Enable Drive
  • Either note down the Client Id and Client Secret or click on DOWNLOAD CLIENT CONFIGURATION and open the generated and downloaded credentials.json file

Alternatively, you can

UPDATE

KeyError: '_module' is an error originating from retrieving a json file with an incorrect structure.

Possible reasons:

  • Using the API Client Library that is expecting a client_secrets.json file - this is different from the credentials.json file you obtain from the Google Drive API Quickstart
  • Storing not valid credentials in the json file
  • Storing the json file in another folder than the py file
  • Using invalid scopes

I understand that you use the WebApplicationClient and need to create respective client credentials for a Web Server.

The json file should have the content of type

{"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"]}}

ALSO:

The scope https://www.googleapis.com/auth/drive.file is not sufficient to list all files on a user's drive - it only gives you access to the files that were created / opened with your app. More information here.

I recommend you for debugging purposes to give your App in the first generous scopes. Once you solve the credentials issue and your App works - see how far you can restrict the scopes without impacting the functionality of your App.