0

I have tried uploading file to Google Drive from my local system using a Python script but I keep getting HttpError 403. The script is as follows:

from googleapiclient.http import MediaFileUpload
from googleapiclient import discovery
import httplib2
import auth

SCOPES = "https://www.googleapis.com/auth/drive"
CLIENT_SECRET_FILE = "client_secret.json"
APPLICATION_NAME = "test"
authInst = auth.auth(SCOPES, CLIENT_SECRET_FILE, APPLICATION_NAME)
credentials = authInst.getCredentials()
http = credentials.authorize(httplib2.Http())
drive_serivce = discovery.build('drive', 'v3', credentials=credentials)
file_metadata = {'name': 'gb1.png'}
media = MediaFileUpload('./gb.png',
                        mimetype='image/png')
file = drive_serivce.files().create(body=file_metadata,
                                    media_body=media,
                                    fields='id').execute()
print('File ID: %s' % file.get('id'))

The error is :

googleapiclient.errors.HttpError: <HttpError 403 when requesting
https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&alt=json&fields=id 
returned "Insufficient Permission: Request had insufficient authentication scopes.">

Am I using the right scope in the code or missing anything ?

I also tried a script I found online and it is working fine but the issue is that it takes a static token, which expires after some time. So how can I refresh the token dynamically?

Here is my code:

import json
import requests
headers = {
    "Authorization": "Bearer TOKEN"}
para = {
    "name": "account.csv",
    "parents": ["FOLDER_ID"]
}
files = {
    'data': ('metadata', json.dumps(para), 'application/json; charset=UTF-8'),
    'file': ('mimeType', open("./test.csv", "rb"))
}
r = requests.post(
    "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart",
    headers=headers,
    files=files
)
print(r.text)
halfer
  • 19,824
  • 17
  • 99
  • 186
Gautam Bothra
  • 565
  • 1
  • 8
  • 23
  • I think that in your situation, it is required to use the refresh token. But I cannot understand about your goal. In your question, there are 2 types of script. Do you want to achieve your goal using [google-api-python-client](https://github.com/googleapis/google-api-python-client)? Or do you want to achieve your goal with using only request module without using google-api-python-client? Can I ask you about your goal? – Tanaike Feb 12 '20 at 07:16
  • Thanks for the prompt response. I want to upload my files or data using any of the script mentioned above. But my first preference will google-api-python-client script. – Gautam Bothra Feb 12 '20 at 07:23
  • @Tanaike refresh token is for requesting a new access token after it has expired thats a completely different error message if your access has expired. – Linda Lawton - DaImTo Feb 12 '20 at 07:36
  • @DaImTo Thank you for your comment. I think that when the expiration time is over, an error of "Invalid Value" occurs. In OP's situation, I had thought that OP wants to refresh the access token and use it when the script is run, and OP wants the sample script for retrieving the refresh token and refreshing the access token. So I had prepared the sample script using the authorization process of Python Quickstart. But if my understanding is not correct, I have to modify it. So can I ask you whether my understanding is correct? I apologize for my poor English skill. – Tanaike Feb 12 '20 at 07:48
  • @Tanaike, For second script your solution will be perfect. for first script i am using client_secret.json file that i have generated from google console oauth2.0. So for first script it won't require refresh token as client_secret.json file does not change until we manually do it. you can share your script as from any of the solution i can achieve my goal. Please provide your solution. – Gautam Bothra Feb 12 '20 at 08:43

6 Answers6

0

"Insufficient Permission: Request had insufficient authentication scopes."

Means that the user you have authenticated with has not granted your application permission to do what you are trying to do.

The files.create method requires that you have authenticated the user with one of the following scopes.

enter image description here

while your code does appear to be using the full on drive scope. What i suspect has happens is that you have authenticated your user then changed the scope in your code and not promoted the user to login again and grant consent. You need to remove the users consent from your app either by having them remove it directly in their google account or just deleteing the credeitnals you have stored in your app. This will force the user to login again.

There is also an approval prompt force option to the google login but am not a python dev so im not exactly sure how to force that. it should be something like the prompt='consent' line below.

flow = OAuth2WebServerFlow(client_id=CLIENT_ID,
                           client_secret=CLIENT_SECRET,
                           scope='https://spreadsheets.google.com/feeds '+
                           'https://docs.google.com/feeds',
                           redirect_uri='http://example.com/auth_return',
                           prompt='consent')

consent screen

If done correctly the user should see a screen like this

enter image description here

Prompting them to grant you full access to their drive account

Token pickle

If you are following googles tutorial here https://developers.google.com/drive/api/v3/quickstart/python you need to delete the token.pickle that contains the users stored consent.

if os.path.exists('token.pickle'):
    with open('token.pickle', 'rb') as token:
        creds = pickle.load(token)
Community
  • 1
  • 1
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • @DalmTo, but i have already given scope access("https://www.googleapis.com/auth/drive") in my app that i have created from google console. – Gautam Bothra Feb 12 '20 at 07:41
  • Thats not what im saying. Im saying you need the user to login again. The access token and refresh token that you have stored in your system are not up to date. You need to force the user to login and consent to your access to their google drive account. You cant just change the scope in your code the user need to agree to the new scope. – Linda Lawton - DaImTo Feb 12 '20 at 09:39
  • @GautamBothra check my edit are you seeing the consent screen with the prompt for full access? – Linda Lawton - DaImTo Feb 12 '20 at 12:01
  • I haven't get this content screen while using the code mentioned by you. – Gautam Bothra Feb 12 '20 at 13:24
  • Thats because you haven't removed the users permission from your app as i stated you needed to do. https://myaccount.google.com/permissions <-- remove your app there. or force the user to login again using prompt='consent' or just delete the access token you have stored in your code. – Linda Lawton - DaImTo Feb 12 '20 at 13:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207666/discussion-between-gautam-bothra-and-daimto). – Gautam Bothra Feb 12 '20 at 13:30
0

Answer:

Delete your token.pickle file and re-run your application.

More Information:

As long as you have the correct set of credentials then all that is required when you update the scopes of your application is to re-obtain a token. Delete the token file located in the application's root folder, then run the application again. If you have the https://www.googleapis.com/auth/drive scope, and the Gmail API enabled in the developer console, you should be good.

References:

Community
  • 1
  • 1
Rafa Guillermo
  • 14,474
  • 3
  • 18
  • 54
0

You can use the google-api-python-client to build a Drive service for using Drive API.

  • Get your Authorization as by following the first 10 steps of this answer.
  • If you want the user to go through consent screen only once, then store the credentials in a file. They include a refresh token that app can use to request authorization after expired.Example

With a valid Drive Service you can upload a file by calling a function like the following upload_file:

def upload_file(drive_service, filename, mimetype, upload_filename, resumable=True, chunksize=262144):
    media = MediaFileUpload(filename, mimetype=mimetype, resumable=resumable, chunksize=chunksize)
    # Add all the writable properties you want the file to have in the body!
    body = {"name": upload_filename} 
    request = drive_service.files().create(body=body, media_body=media).execute()
    if getFileByteSize(filename) > chunksize:
        response = None
        while response is None:
            chunk = request.next_chunk()
            if chunk:
                status, response = chunk
                if status:
                    print("Uploaded %d%%." % int(status.progress() * 100))
    print("Upload Complete!")

Now pass in the parameters and call the function...

# Upload file
upload_file(drive_service, 'my_local_image.png', 'image/png', 'my_imageination.png' )

You will see the file with the name: my_imageination.png in your Google Drive root folder.

More about the Drive API v3 service and available methods here.


getFileSize() function:

def getFileByteSize(filename):
    # Get file size in python
    from os import stat
    file_stats = stat(filename)
    print('File Size in Bytes is {}'.format(file_stats.st_size))
    return file_stats.st_size

Uploading to certain folder(s) in your drive is easy...

Just add the parent folder Id(s) in the body of the request.

Here are the properties of a File. parents[] property of File

Example:

request_body = {
  "name": "getting_creative_now.png",
  "parents": ['myFiRsTPaRentFolderId',
              'MyOtherParentId',
              'IcanTgetEnoughParentsId'],
}

Aerials
  • 4,231
  • 1
  • 16
  • 20
  • Hello Aerials, Sorry for the late response. The file is getting uploaded but after that it is showing error :- "httplib2.RedirectMissingLocation: Redirected but the response is missing a Location: header." Please help me to resolve this. – Gautam Bothra Feb 17 '20 at 05:38
  • And also i want to upload the file in a particular folder. How can i do that? – Gautam Bothra Feb 17 '20 at 05:49
  • Remember to add the parent folder Id in the body of the request. – Aerials Feb 17 '20 at 08:03
0

To use the scope 'https://www.googleapis.com/auth/drive' you need to submit the google app for verification.

Find the image for scope

So use the scope 'https://www.googleapis.com/auth/drive.file' instead of 'https://www.googleapis.com/auth/drive' to upload files without verification.

Also use SCOPES as list.

ex: SCOPES = ['https://www.googleapis.com/auth/drive.file']

I can successfully upload and download the files to google drive by using the above SCOPE.

0

I found the solution for uploading a file to google drive. Here it is:

import requests
import json
url = "https://www.googleapis.com/oauth2/v4/token"

        payload = "{\n\"" \
                  "client_id\": \"CLIENT_ID" \
                  "\",\n\"" \
                  "client_secret\": \"CLIENT SECRET" \
                  "\",\n\"" \
                  "refresh_token\": \"REFRESH TOKEN" \
                  "\",\n\"" \
                  "grant_type\": \"refresh_token\"\n" \
                  "}"
        headers = {
            'grant_type': 'authorization_code',
            'Content-Type': 'application/json'
        }

        response = requests.request("POST", url, headers=headers, data=payload)

        res = json.loads(response.text.encode('utf8'))


        headers = {
            "Authorization": "Bearer %s" % res['access_token']
        }
        para = {
            "name": "file_path",
            "parents": "google_drive_folder_id"
        }
        files = {
            'data': ('metadata', json.dumps(para), 'application/json; charset=UTF-8'),
            # 'file': open("./gb.png", "rb")
            'file': ('mimeType', open("file_path", "rb"))
        }
        r = requests.post(
            "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart",
            headers=headers,
            files=files
        )
        print(r.text)

For generating client id, client secret and refresh token, you can follow the link :- click here

Gautam Bothra
  • 565
  • 1
  • 8
  • 23
0

Maybe the question is a little bit outdated, but I found an easy way to upload files on Google Drive from python

pip install gdrive-python

Then you have to allow the script to upload files on your Google account with this command and follow the instructions:

python -m drive about

Finally, upload the file:

from gdrive import GDrive

drive = GDrive()
drive.upload('path/to/file')

More info on the GitHub repo: https://github.com/vittoriopippi/gdrive-python

Dave Radcliffe
  • 644
  • 4
  • 11
Vittorio
  • 213
  • 3
  • 7