44

I have created a server side application in PHP that's supposed to work with Google Spreadsheets.

I'm able to authenticate successfully with OAuth 2.0 authentication, but when requesting the list of the spreadsheets from Google, I only get the spreadsheets shared with the service account by the spreadsheet owner.

Is there a way that service account could retrieve all the spreadsheets owned by my main account not the service one, including those not explicitly shared with the service account?

Also I still want to keep the spreadsheets private so noone can access them without my permission, but I need the service account to have full access to both existing and new spreadsheets.

Any advice is appreciated.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
ZurabWeb
  • 1,241
  • 1
  • 12
  • 21
  • Please clarify: how does your question differ from http://stackoverflow.com/questions/14187030/how-do-i-authorize-a-google-drive-service-account-access-to-a-google-account-wit or http://stackoverflow.com/questions/14124946/google-drive-service-account-view-files-from-another-account – Foo Bar Nov 21 '14 at 20:02
  • 1. Spreadsheets is not a part of Google Drive API as I understand this far. 2. I don't think I can grant service account the access being authorized with the service account. – ZurabWeb Nov 21 '14 at 20:16
  • How to do the opposite? I have spreadsheet which is creates by issuing google API calls under service account and I can'taccess it. It says that access is restricted. How to explain to Google that I want create spreadsheet which is shared to others and not just to service account? – Mr D Feb 11 '22 at 07:25

4 Answers4

38

Here is a sample script that uses a service account to read the contents of a Google Spreadsheet's sheet. Have a look at the README for instructions to set it up:

https://github.com/juampynr/google-spreadsheet-reader

Juampy NR
  • 2,618
  • 1
  • 25
  • 21
  • 27
    What I got out of this README is that you must share the Google Sheet with the service account email address. That solved my problem. – Dustin Michels Mar 14 '19 at 19:43
  • Yes, that's true. For some reason for a new Sheet I'm working on when I try to share it with a service account email, Sheets tells me there is an error. Why might this be the case? – ariestav Oct 06 '20 at 20:23
15

You have to change approach:

  1. Create a service account in Drive. You can manage and use this account only with the API (not with the web interface as usually)

  2. With the Drive API you can list, create, update, delete and change the permissions of the files. When you create a new file, you can share it - always with API - to the users you want, make the new file public or change the ownership.

Please take a look: https://developers.google.com/drive/v2/reference/permissions

Marcello Verona
  • 540
  • 1
  • 6
  • 10
2

I am assuming you use Google Apps and not a personal GMail account. You can use your service account to impersonate your main account.

Note that here I am talking of a service account in the way Google means it : a private key (in p12 format) and an identifier. This is not a Google Apps account used for technical purposes. More information here.

This is done by :

  1. Authorizing your service account on your Google Apps domain
  2. Modify the code you use to generate the API credentials

The steps are exactly the same for the Spreadsheet API and for the Drive API.

To first authorize your service account to impersonate the domain users, you can follow this documentation. Here are the basic steps. You must be a domain super administrator to perform this task.

  1. Go to your Google Apps domain’s Admin console : https://admin.google.com
  2. Select Security from the list of controls. If you don't see Security listed, select More controls from the gray bar at the bottom of the page, then select Security from the list of controls.
  3. Select Advanced settings from the list of options.
  4. Select Manage third party OAuth Client access in the Authentication section.
  5. In the Client name field enter the service account's Client ID. In the One or More API Scopes field enter the list of scopes that your application should be granted access to. In your case that would be at least the Spreadsheet API scope : https://spreadsheets.google.com/feeds

When this is done, you need to update your code so that it impersonates your main account :

$key = file_get_contents($SERVICE_ACCOUNT_PKCS12_FILE_PATH);
$auth = new Google_AssertionCredentials(
      'YOUR_SERVICE_ACCOUNT_EMAIL',
      array('https://spreadsheets.google.com/feeds'),
      $key);
$auth->sub = 'yourmainaccount@domain.com';

You can then use the $authvariable to generate the OAuth tokens you need to access the spreadsheet API. I am not sure which client you use for this so the way you inject the access token will depend on which client you use.

Also, note that this token will expire after 1 hour, and then the API will start returning Session Expired errors. If your client does not handle this automatically, you will need to catch the error and regenerate the token.

David
  • 5,481
  • 2
  • 20
  • 33
  • Unfortunately it's Gmail account. – ZurabWeb Dec 01 '14 at 15:21
  • Then you should give your application the rights of your main account not a service account. You can restricts those rights to spreadsheet only. – David Dec 02 '14 at 02:01
  • 1
    But I can't have a .p12 authentication file for the main account, only for service one. – ZurabWeb Dec 02 '14 at 20:06
  • That is OAuth 2.0 for main account without user interaction will not work. – ZurabWeb Dec 02 '14 at 20:07
  • User interaction is required only once. The first user interaction will provide you an access token and a refresh token. Save those tokens somewhere and you will be able to make API calls without user interaction. – David Dec 03 '14 at 00:38
  • 1
    There are more problems than just that. Users will need to be logged into their google account, and that's if they actually have one, otherwise they'll need to sign up, which would require them to give out their phone number to google and stuff like that. Problem. – ZurabWeb Dec 03 '14 at 15:55
  • They don't need to, as long as your application uses the access and refresh tokens from your main account, that you will have stored in your database. No other token from any Google account is required. – David Dec 03 '14 at 17:01
  • So basically if the app accesses google with my main google account main@gmail.com, whenever the app loads first time, I will need to allow access, being logged in under main@gmail.com, and then all other users whether they are logged into google or not, will be able to see the spreadsheets in my google account? This actually might work if that's so. – ZurabWeb Dec 03 '14 at 17:12
0

Here's how I did it:

Step 1: Create a service account

  1. Head to console.cloud.google.com
  2. Click IAM & Admin
  3. On the left pane, click Service Accounts
  4. Click Create service account
  5. Proceed with creating a service account, at the end you'll get an email for that service account (e.g xyz-abcd@username.iam.gserviceaccount.com)
  6. After creation is done, click on the email from the menu, then head to the KEYS tab, create a new key: ADD KEY --> Create new key (choose JSON).

Step 2: Using the service account

Now you will link the service-account with a spreadsheet on Google drive, open the spreadsheet, File -> Share -> Share with others and add the service account email from above. Make as an editor/viewer as desired.

After doing this, now you can use the service account using a client library (in my case I used Python client library along with but same concept will apply to the PHP library).

Here's a Python example that reads a range of cells:

from googleapiclient import discovery
from google.oauth2 import service_account
from pprint import pprint

credentials = service_account.Credentials.from_service_account_file('service-account.json')

service = discovery.build('sheets', 'v4', credentials=credentials)
spreadsheet_id = 'XYZ'  # TODO: Update placeholder value.
range_ = 'A1:B9'
request = service.spreadsheets().values().get(spreadsheetId=spreadsheet_id, range=range_)
response = request.execute()
pprint(response)
ahmelq
  • 593
  • 7
  • 11