googleapis / google-api-python-client

🐍 The official Python client library for Google's discovery based APIs.
https://googleapis.github.io/google-api-python-client/docs/
Apache License 2.0
7.78k stars 2.42k forks source link

What should be the port value for working on the production server? #755

Closed Lepiloff closed 5 years ago

Lepiloff commented 5 years ago

When developing on a local machine I used standart code to authorize and work with Google disk

creds = None
cred = os.path.join(settings.BASE_DIR, 'credentials.json')
# The file token.pickle 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.pickle'):
    with open('token.pickle', 'rb') as token:
        creds = pickle.load(token)
# 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:
        flow = InstalledAppFlow.from_client_secrets_file(
            cred, SCOPES)
        creds = flow.run_local_server(port=8080)
    # Save the credentials for the next run
    with open('token.pickle', 'wb') as token:
        pickle.dump(creds, token)

I use 8080 as the port parameter value. This works. When deploying a project on a production server, I get a message port is already in use.

Server: Ubuntu 18.04, Nginx, uwsgi, Django

I've changed the port to 5000, everything works on the local machine. I tried to run it on a production server and got a 504 error. I checked the uwsgi log and saw that the end of the file contains a link to authorization.

Screenshot from 2019-09-12 16-16-08

On the local machine, this link automatically opens in a new window to log on to the account. If try to run the production server again, I will get the [Errno 98] Address already in use error and this error is saved until reboot uwsgi. After the reboot, everything repeats itself again.

I've tried many different port meanings, the same problem with everyone. I think the problem here is not in the specific meaning. After restart uwsgi there is an attempt of authorization (i.e. the value of the port is accepted) but the redirection by reference to OAuth2 does not occur. And it's quite strange that when try again, the error[Errno 98] Address already in use

What values should I use?

busunkim96 commented 5 years ago

Hi @Lepiloff,

It looks like you're using InstalledAppFlow, which is for applications installed on devices. It sounds like you are developing a web server application?

run_local_server uses a default host of localhost for the redirect_uri, which is fine when you run the project locally but won't work once your project is deployed.

Please take a look at the Python documentation for the OAuth2 Web Server flow. There are code snippets and a complete example using flask that should be helpful.

Please do let us know if you have additional questions.

Lepiloff commented 5 years ago

@busunkim96 thank you for answering. Yes, I use web server application. Google API documentation in some places not very clear. Now I know that run_local_server uses only for locall projects. It is very strange that there are no good examples on the Internet for using Django and Google API for my purposes. I will try to deal with this problem

Lepiloff commented 5 years ago

I'm trying to change the code according to the link you gave me. My code

import google_auth_oauthlib.flow
from googleapiclient.discovery import build
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect

import os
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

SCOPES = 'https://www.googleapis.com/auth/drive'
cred = os.path.join(settings.BASE_DIR, 'credentials.json')

Function for upload file:

def file_to_drive(request, import_file=None):

    state = request.session['state']
    if state is None:
        authorize(request)
    else:
        flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
          cred, scopes=SCOPES, state=state)
        flow.redirect_uri = "http://localhost:8000/oauth2callback"
        authorization_response = request.build_absolute_uri()
        flow.fetch_token(authorization_response=authorization_response)
        credentials = flow.credentials

        service = build('drive', 'v3', credentials=credentials)
        file_metadata = {
            'name': 'My Report',
            'mimeType': 'application/vnd.google-apps.spreadsheet'
        }
        media = MediaFileUpload(import_file,
                                mimetype='text/html',
                                resumable=True)
        file = service.files().create(body=file_metadata,
                                            media_body=media,
                                            fields='id').execute()
        print('File ID: %s' % file.get('id'))
    return (f"https://docs.google.com/document/d/{file.get('id')}/edit")

And function for user authorization

def authorize(request):

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      cred, scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000/oauth2callback"
    authorization_url, state = flow.authorization_url(
      access_type='offline',
      include_granted_scopes='true')
    request.session['state'] = state
    return HttpResponseRedirect(authorization_url)

urls.py

path('oauth2callback', authorize, name='authorize'),
path('to_drive', file_to_drive, name='file_to_drive'),

In function file_to_drive searching for the value of the state parameter from the session, if it is not found, the authorize function is called. In the end, I get a message

oauthlib.oauth2.rfc6749.errors.MismatchingStateError: (mismatching_state) CSRF Warning! State not equal in request and response.

Error traseback

File "/home/y700/projects/CV/cv-base/ems/base/utils.py", line 119, in file_to_drive
    flow.fetch_token(authorization_response=authorization_response)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/google_auth_oauthlib/flow.py", line 263, in fetch_token
    self.client_config['token_uri'], **kwargs)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/requests_oauthlib/oauth2_session.py", line 208, in fetch_token
    state=self._state)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py", line 203, in parse_request_uri_response
    response = parse_authorization_code_response(uri, state=state)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 268, in parse_authorization_code_response
    raise MismatchingStateError()
oauthlib.oauth2.rfc6749.errors.MismatchingStateError: (mismatching_state) CSRF Warning! State not equal in request and response.
[13/Sep/2019 13:19:52] "GET /to_drive HTTP/1.1" 500 98701

the error is caused by a string flow.fetch_token(authorization_response=authorization_response)

busunkim96 commented 5 years ago

Hi @Lepiloff. It looks like you're getting an error about mismatching states. There is a bit more info about state here in the description of the parameters. State needs to match in the request and response.

I think you'll want to write one more function.

In the flask example down towards the bottom of the page, you'll see there are two routes for the OAuth flow.

@app.route('/oauth2callback')
def oauth2callback():
  # Specify the state when creating the flow in the callback so that it can
  # verified in the authorization server response.
  state = flask.session['state']

  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  # Use the authorization server's response to fetch the OAuth 2.0 tokens.
  authorization_response = flask.request.url
  flow.fetch_token(authorization_response=authorization_response)

  # Store credentials in the session.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  credentials = flow.credentials
  flask.session['credentials'] = credentials_to_dict(credentials)

  return flask.redirect(flask.url_for('test_api_request'))
busunkim96 commented 5 years ago

Closing due to lack of activity. Please file a new issue if you are experiencing this problem.

jonathanmarquezj commented 4 years ago

Gracias ^^

akashsenta13 commented 3 years ago

Hello All, Please let me know if anybody has any example for Django with Youtube Data API.

dhwaj1902 commented 2 years ago

Hey @Lepiloff can you help me regarding this, how do you solve the problem you are facing, like how do you implement the code for web server using python. I am not able to get any relatable answer on the entire internet. Actually I want to send mails to users using gmail api. And it is running fine on localhost( using desktop application), but when I moved this to my server it started showing me the error [Errno 98] Address already in use. But the port I am activating for the authentication is an empty port on my server.

Tuno555 commented 1 year ago

Hey @Lepiloff can you help me regarding this, how do you solve the problem you are facing, like how do you implement the code for web server using python. I am not able to get any relatable answer on the entire internet. Actually I want to send mails to users using gmail api. And it is running fine on localhost( using desktop application), but when I moved this to my server it started showing me the error [Errno 98] Address already in use. But the port I am activating for the authentication is an empty port on my server.

hi dhwaj1902, I have the same issue when deploy my app to server, do you have a solution for this yet?