I have to read an excel file on google drive. I need to connect automatically to google drive without prompt any form... a bot must read document... I have this code:
$service_account_file = dirname(__FILE__) . '/key-85ac95ab1e62.json';
$spreadsheet_id = 'the_id_inside_the_url_of_the_file';
$spreadsheet_range = 'Sheet1!A1:A1';
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $service_account_file);
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(Google_Service_Sheets::SPREADSHEETS);
$service = new Google_Service_Sheets($client);
$result = $service->spreadsheets_values->get($spreadsheet_id, $spreadsheet_range);
var_dump($result->getValues());
Something goes wrong... I explain how I generated the key-*.json file:
On Google Console Developer i clicked on Service Account, then i created a new one and downloaded the json.. this json is the key-*.json file. I have added this file in my project (as you can see on first row)... Here i copied the Account Service Idthat I will use later...
Now I explain how I get the spreadheet_id... I went on my google drive, right click on the file and I shared it with the Account Service Id (copied before) and an url has been generated... if I copy and past the url in the browser everything works but not in the project... i take the spreadsheet_id from url
this is the error I receive :
PHP Fatal error: Uncaught Google_Service_Exception: {
"error": {
"code": 404,
"message": "Requested entity was not found.",
"errors": [
{
"message": "Requested entity was not found.",
"domain": "global",
"reason": "notFound
}
],
"status": "NOT_FOUND
}
}
What's wrong? the file exists and i cann see it in the browser using the same url I use in the php file... so I think it is a connection or login problem...
any idea?
This is the usual problem with Service Accounts. Refer to any of these https://stackoverflow.com/search?q=404+service+account
In simple terms a Service Account is NOT the same as the Google Account that you used to create it. Either don't use Service Accounts, or share the file from its own account to the Service Account to give it access.
Related
I'm writing a script in PHP to automate the process of grabbing data from a Database and inserting it into a google sheet.
Since it's going to be running at 15 minute intervals and Oauth consent screen isn't really the best option.
Is there another option to get the necessary authorization to write to the sheet without the need of signing in via browser?
Most of the examples I've seen in the documentation and online use a consent screen.
If I try this:
$client = new Google_Client();
$service = new Google_Service_Sheets($client);
Which would work with the Text-to-Speech API I get this (naturally):
> PHP Fatal error: Uncaught Google\Service\Exception: { "error": {
> "code": 401,
> "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid
> authentication credential. See
> https://developers.google.com/identity/sign-in/web/devconsole-project.",
> "errors": [
> {
> "message": "Login Required.",
> "domain": "global",
> "reason": "required",
> "location": "Authorization",
> "locationType": "header"
> }
> ],
> "status": "UNAUTHENTICATED" } }
The link in the error message just guides you through making a consent screen. From what I could find online if i were to only be reading a file there wouldn't be a problem, but writing to it, even if i set the sheet to public, requires authentication.
Note: This is all running in a Gcloud VM instance
You can use a service account and then share the sheet with that service account.
Go to console.cloud.google.com and login with the account containing the sheet
Add the Google Sheets API (go to APIs and Services, look up Google Sheets API, add it)
Go back to APIs and Services and click on Credentials
Click on Create credentials -> Service account
Fill in required stuff (mainly Service account name, and give it Owner role) and press done
Click on the service account you just created
Go to keys tab -> Add key -> Create new key -> JSON
This should give you a key to use. This is your client_secret.json. Use
$client->setAuthConfig('./client_secret.json') or
$client->setAuthConfigFile('./client_secret.json')
depending on the version of the php google api client you are using.
Also, make sure you set the appropriate scope using
$client->setScopes([\Google_Service_Sheets::SPREADSHEETS]);
Share the sheet from your drive with the created service account and give it editorial permissions
Calling Google API got this message:
{
"error": {
"errors": [
{
"domain": "androidpublisher",
"reason": "permissionDenied",
"message": "The current user has insufficient permissions to perform the requested operation."
}
],
"code": 401,
"message": "The current user has insufficient permissions to perform the requested operation."
}
}
or this error message (ADDED):
{
"error": {
"errors": [
{
"domain": "androidpublisher",
"reason": "projectNotLinked",
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
}
],
"code": 403,
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
}
}
I follow all indications I found and I keep having this error.
ON MY SYSTEM
My code
try {
ini_set('max_execution_time', 3000);
$client = new Google_Client();
if ($credentials_file = $this->checkServiceAccountCredentialsFilePlay()) {
// set the location manually
$client->setAuthConfig($credentials_file);
} elseif (getenv('GOOGLE_APPLICATION_CREDENTIALS')) {
// use the application default credentials
$client->useApplicationDefaultCredentials();
} else {
$rv= "missingServiceAccountDetailsWarning()";
return [$rv];
}
$client->addScope("https://www.googleapis.com/auth/androidpublisher");
$serviceAndroidPublisher = new \Google_Service_AndroidPublisher($client);
$servicePurchaseSubscription = $serviceAndroidPublisher->purchases_subscriptions;
$rv = $servicePurchaseSubscription->get(
"com.my.app",
"sub1month",
"ajgbkxxxxxxxxx.AO-J1OxTOKENTOKENTOKEN-"
);
} catch (\Exception $e) {
return $e->getMessage();
}
The credential file
{
"type": "service_account",
"project_id": "project-id",
"private_key_id": "abababababababababababababababababa",
"private_key": "-----BEGIN PRIVATE KEY-----KEYBASE64=\n-----END PRIVATE KEY-----\n",
"client_email": "google-play-account#project-id.iam.gserviceaccount.com",
"client_id": "123450000000000000000",
"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_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-play-account%40project-id.iam.gserviceaccount.com"
}
ON GOOGLE PLAY CONSOLE
I link the project to the google play console
I add the Service account to the google play console
It's present in the user menu of google play console
And I give him all permission:
ON GOOGLE API DEVELOPER CONSOLE
ADD FYI: my "project-id" is under an organization.
In google developer console I gave all possible permission to the service account:
And of course I've enabled the google Play Android Developer Api (showing my failures):
I had the same problem.
In my case, I was using the google app engine (python) as a backend service. In the beginning, I linked a new Google cloud project in the Google Play Console. I'm not entirely sure if it's the main reason that I got a 401 'insufficient permissions' error, but after switching the linked project to my cloud project (which I used for the app engine) it worked the next day. Right after switching accounts, I received the 403 'projects not linked' error. So, I'm guessing that Google doesn't recognize the change of the linked projects immediately and you need to wait for a few hours.
If you are using app engine, you have to make sure that your default service account for the app engine has a JSON credential file. It's not created by default.
Here is the python code:
if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
# production environment
credentials = oauth2client.contrib.appengine.AppAssertionCredentials(scope='https://www.googleapis.com/auth/androidpublisher')
http = credentials.authorize(httplib2.Http(memcache))
response = googleapiclient.discovery.build('androidpublisher', 'v3').purchases().subscriptions()\
.get(packageName=package_name, subscriptionId=subscription.product_id, token=subscription.token)\
.execute(http)
else:
# local environment
# setting the scope is not needed because the api client handles everything automatically
credentials = google.oauth2.service_account.Credentials.from_service_account_file('local_dev.json')
response = googleapiclient.discovery.build('androidpublisher', 'v3', credentials=credentials).purchases().subscriptions()\
.get(packageName=package_name, subscriptionId=subscription.product_id, token=subscription.token)\
.execute()
I've created a service account for use with the YouTube Content ID API, I'm following the steps under Set up your service account on:
https://developers.google.com/youtube/partner/guides/oauth2_for_service_accounts
The steps seem to be a bit outdated, but I'm managed to create a service account and I've got the email address. I didn't know if I needed to assign a Role so I didn't.
I visited the Content ID users page and invited the service account user:
The problem is I get the error the request could not be completed, but when I refresh the page, the user appears to be added successfully.
Well, when I'm making requests using this service account, I'm getting forbidden errors. For example the below error is when I'm trying to get a list of claims.
{
"error": {
"errors": [
{
"domain": "global",
"reason": "forbidden",
"message": "Forbidden"
}
],
"code": 403,
"message": "Forbidden"
}
}
But some requests work, like listing/inserting labels, listing content owners.
All the permissions are assigned for the user so I'm not sure what the reason for the forbidden errors are.
Here's the PHP code:
$OAUTH2_KEY_FILE = 'key.json';
$client = new \Google_Client();
$client->setAuthConfig($OAUTH2_KEY_FILE);
$client->setScopes([\Google_Service_YouTubePartner::YOUTUBEPARTNER]);
$youtubePartner = new \Google_Service_YouTubePartner($client);
dd($youtubePartner->claims->listClaims([
'onBehalfOfContentOwner' => 'XXX'
]));
I created a service account credential in Google and tried to change the thumbnail of a video I own (it's uploaded in my verified channel). The account is, by the way, a managed account (indeed, it's a company email). The code below apparently logs in using the credentials in the JSON file downloaded from the Google API backend. IMAGE_MIME is image/jpeg, CHUNK_SIZE_BYTES is 1 * 1024 * 1024.
putenv("GOOGLE_APPLICATION_CREDENTIALS=" . CLIENT_ACCOUNT_FILE);
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->setScopes(['https://www.googleapis.com/auth/youtube', 'https://www.googleapis.com/auth/youtube.upload']);
$client->setDefer(true);
$youtube = new Google_Service_YouTube($client);
try {
$set_request = $youtube->thumbnails->set($video_id);
$media = new Google_Http_MediaFileUpload(
$client,
$set_request,
IMAGE_MIME,
null,
true,
CHUNK_SIZE_BYTES
);
$media->setFileSize(filesize($image_path));
$status = false;
$handle = fopen($image_path, "rb");
while (!$status && !feof($handle)) {
$chunk = fread($handle, CHUNK_SIZE_BYTES);
$status = $media->nextChunk($chunk);
}
fclose($handle);
} catch (Exception $e) {
echo "ERROR -> " . $e->getMessage();
}
$client->setDefer(false);
Sadly, it throws an exception. Running getMessage(), it pops out this JSON:
{
"error": {
"errors": [
{
"domain": "youtube.thumbnail",
"reason": "forbidden",
"message": "The thumbnail cant be set for the specified video. The request might not be properly authorized.",
"locationType": "parameter",
"location": "videoId"
}
],
"code": 403,
"message": "The thumbnail cant be set for the specified video. The request might not be properly authorized."
}
}
I directly access the video ID, the channel is mine (and there's only one channel), it doesn't throw a login exception (it did until I made it work), and the image file exists. Has anyone had this issue before?
Edit
I decided to log into my personal account and follow the same process of verification, API activation, service account JSON download and script running. The error is absolutely the same, even when everything in there should be working now for a non-domain account, after Jay's answer.
I tried, just because, to use the company credentials with my personal video and try this. Same error. It's like I'm missing something on the whole process. I also checked the value of getenv and it's correct, and not setting it would pop a "Could not load the default credentials.", so in the end it's grabbing the proper credentials, although not giving access to them them somehow.
Service accounts are not members of managed G Suite domains (company accounts) and do not automatically have rights to any data that the service account creator user account has. See the note on Google's domain wide delegation setup:
Note: Although you can use service accounts in applications that run from a G Suite domain, service accounts are not members of your G Suite account and aren’t subject to domain policies set by G Suite administrators. For example, a policy set in the G Suite admin console to restrict the ability of G Suite end users to share documents outside of the domain would not apply to service accounts.
Options for you would be:
Actually perform domain wide delegation as described above and act as your G Suite user, not the service account.
Share edit access of the Youtube video with the service account email address.
I'm trying to fetch a given email from my own inbox (as project owner) using the Google API 2.0.0 RC4 for PHP.
I have a project setup, a downloaded authentication file in JSON format and I have enabled the gmail API in the Google Developers Console.
But when I run the following code I get a "Bad Request" error with reason "failedPrecondition". Now this leads me to believe that there is something wrong with the access rights, but I can't figure out what I need to do here.
Code:
<?php
require_once("google-api-php-client-2.0.0-RC4/vendor/autoload.php");
$client = new Google_Client();
// set the scope(s) that will be used
$client->setScopes(["https://www.googleapis.com/auth/gmail.readonly"]);
$client->setApplicationName("MyProject");
// set the authorization configuration using the 2.0 style
$client->setAuthConfigFile("myAuthFile.json");
$gmailHandle = new Google_Service_Gmail($client);
$messages = $gmailHandle->users_messages->get("myemail#mydomain.com", "12343445asd56");
echo json_encode($messages);
?>
And the complete error looks like this:
Fatal error: Uncaught exception 'Google_Service_Exception' with message '{ "error": { "errors": [ { "domain": "global", "reason":
"failedPrecondition", "message": "Bad Request" } ], "code": 400,
"message": "Bad Request" } } '
I just ran into this and finally found the solution. You are getting that error because you are using a service account without specifying a "subject" as part of the configuration. That is required for the API to know which email address you are acting as when using the service account. You can see it attempting to use this subject in Google_Client::createApplicationDefaultCredentials
The solution is to add the following line in the code after you have called setAuthConfig()
$client->setConfig('subject', 'you#email.com');
Be sure to then use the keyword 'me' when fetching the messages from the inbox as you are now acting as the email address which you have configured as 'subject'.