0

I am trying to create a python app to add email attachments (gmail) to google drive. I have used the code from the youtuber Jie Jenn as a base but have run into issues around redirect_uri. I have tried other code examples as well, which all suffer the same fate

Background: My workplace is about to go into industrial action and we have 400+ workers. We have set up a fighting fund to compensate those who are docked pay for industrial action. In order for this to happen efficiently I have set up a gmail account where they can email their payslips. However, that could get incredibly arduous going through all the attachments every pay day. To lighten the load, I'd like to have each payslip placed into a directory for that month. From here I may be able to do other things like scrape the pdf for amount docked. Depending on how much time I have, I will add functionality as I go. We file for industrial action on Monday, so I am fast running out of time. Otherwise I would just persistently stumble along until the issue is solved. I am not an experienced coder, just someone on a constant learning journey and I find it relaxing to sit and learn new skills.

Issue: So I have logged into the google cloud dashboard and created a project. Created credentials of type 'OAuth client ID'. It is of type 'Web application' and I have set an Authorized redirect URI to http://domain.i.registered.last.night The domain A record is pointed to a linux server I have leased and it resolves fine through my ISP dns, and through various online DNS services I have tried. I have the credentials.json or client-secrets.json depending on which bit of code I am trying to use (I have tried running a few examples) and it definately says in there "redirect_uris":["http://domain.i.registered.last.night"]

However, in all examples I have tried I stumble across this message when I run the code: Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=(removed).apps.googleusercontent.com&**redirect_uri=http%3A%2F%2Flocalhost%3A51159%2F**&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.modify&state=(removed)&access_type=offline

If I copy/paste that link into my browser, I get a redirect_uri_mismatch error obviously because "localhost" is not set as the redirect_uri in my app permissions on the google dashboard. I am not sure at this stage what library is producing that link, but I would presume it was supposed to use the json file to set the redirect and if it was not the same as what was set in the google app permissions OAuth fails(as it does here)

Is anyone able to help? Thankyou

RedChair
  • 43
  • 7
  • Sounds like you are following installed app samples. you need to create a web app i normally use flask personally. – Linda Lawton - DaImTo Aug 06 '23 at 07:48
  • Thank you Linda, you've provided another term I can search. I will have a look at "flask" in this context and hopefully it will provide an easy solution I can implement. I think you might be right as I have selected "Web Application" on the google cloud console but there is hints in the example code I was trying to get working that said it was "InstalledAppFlow". – RedChair Aug 06 '23 at 08:29
  • I might have a sample floating around but its probably not for Gmail I will have to look – Linda Lawton - DaImTo Aug 06 '23 at 10:32
  • I feel like I am missing something fundamentally simple here. I've tried to run a simple web server on localhost. Maybe I just need to somehow get a CLI web browser with javascript support but I have tried Browsh which wont let me select links, elinks which is insufficient. If anyone knows of a simple CLI browser with JS support that I can run on the server please, let me know! – RedChair Aug 08 '23 at 02:02

1 Answers1

0

You need to understand that there is a difference between a web app and an installed app. An installed app will run on the machine the user is using it from. The consent screen is opened on that machine. A web app needs to open the consent screen on the users browser not the web server. The client's for these types are different installed app doesn't need to have a redirect uri its always 127.0.0.1, a web app needs to allow you to include the redirect uri to your website. Because of this the code is different for these two types of clients. The sample you are following is for an installed app its not going to work hosted.

The following is my basic example using flask for web auth.

# https://github.com/googleads/googleads-python-lib/wiki/API-access-on-behalf-of-your-clients-%28web-flow%29
import os

import flask
from flask import Flask,redirect,render_template,url_for, request
app = Flask(__name__, template_folder='templates')
import google.auth.exceptions
import google_auth_oauthlib.flow
import ssl
context = ssl.SSLContext()
context.load_cert_chain('C:\Development\FreeLance\GoogleSamples\Python\cert.pem', 'C:\Development\FreeLance\GoogleSamples\Python\key.pem')
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.errors import HttpError
from googleapiclient.discovery import build
import google_auth_oauthlib.flow

SCOPES = ['https://mail.google.com/']

REDIRECT_URI = 'https://127.0.0.1:5000/oauth2callback'

CREDENTIALS = 'C:\Development\FreeLance\GoogleSamples\Credentials\CredWebEverything.json'

def get_flow():

    # Initialize the flow using the client ID and secret downloaded earlier.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CREDENTIALS,
        scopes= SCOPES,
    )
    # Indicate where the API server will redirect the user after the user completes
    # the authorization flow. The redirect URI is required.
    flow.redirect_uri = REDIRECT_URI

    return flow


def redirect_user():

    flow = get_flow()
    # Generate URL for request to Google's OAuth 2.0 server.
    # Use kwargs to set optional request parameters.
    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='false',
        # Forces a new refresh token when we authorize the application a second time.
        #prompt= "consent"
        )

    return authorization_url, state

@app.route('/login')
def login():
    authorization_url, state = redirect_user()
    return flask.redirect(authorization_url)

@app.route('/listmessages')
def gmail_list_messages():
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        try:
            creds = Credentials.from_authorized_user_file('token.json', SCOPES)
            print(f'Credentials exist refreshing.')
            creds.refresh(Request())
        except google.auth.exceptions.RefreshError as error:
            # if refresh token fails, reset creds to none.
            creds = None
            print(f'An error occurred: {error}')
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            # If it's not logged in then it's going to force it to.
            authorization_url, state = redirect_user()
            print(f'Credentials do not exist requesting authorization.')
            return flask.redirect(authorization_url)
    try:
        service = build('gmail', 'v1', credentials=creds)

        # Call the Gmail v1 API
        results = service.users().messages().list(
            userId='me').execute()
        messages = results.get('messages', [])
    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')

    return render_template("mail.html", data=messages)



@app.route('/')
def index():
    return render_template('index.html', title="Home Page")

@app.route('/oauth2callback')
def oauth2callback():
    flow = get_flow()

    auth_code = request.args['code']
    flow.fetch_token(code=auth_code)
    credentials = flow.credentials

    # saving the credentials for later. Note: A refresh does not return a new refresh token.
    if not os.path.exists('token.json') or credentials.refresh_token:
        print(f'Storing credentials: {credentials.to_json()}')
        with open('token.json', 'w') as token:
            token.write(credentials.to_json())

    return redirect("/listmessages")

if __name__ == '__main__':
    # Bind to PORT if defined, otherwise default to 5000.
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port, ssl_context=context)
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449