Google Client for Calendar service responding with a 404 - php

Target
I'm trying to integrate Google Calendar API to my company website to automate event creation upon some events.
The events will be added to a company calendar and displayed to the users, so i don't need the users to authenticate, i will not work on their own calendars.
What i've done
made a OAuth 2.0 service account as described in the Google OAuth 2.0 for Server to Server Applications docs
created, downloaded and added the credentials file to my project, and added its path to the environment variable GOOGLE_APPLICATION_CREDENTIALS;
followed the docs example to authorize my requests using the service account and the docs to create events.
Here's a short version of my sourcecode:
$client = new Google_Client();
$client->setApplicationName("XXX");
$client->addScope(Google_Service_Calendar::CALENDAR); // "https://www.googleapis.com/auth/calendar"
$client->useApplicationDefaultCredentials();
$service = new Google_Service_Calendar($client);
$event = new Google_Service_Calendar_Event(/* array with event data */);
$calendarId = /* calendar id taken from calendar Settings and Sharing on calendar.google.com*/;
$event = $service->events->insert($calendarId, $event);
Compared to the example (lines 43:52) I didn't manually check for the credential files but gone straight for useApplicationDefaultCredentials().
The error
The logged errror from my code is
[2020-10-19 14:29:19] local.ERROR: {
"error": {
"errors": [
{
"domain": "global",
"reason": "notFound",
"message": "Not Found"
}
],
"code": 404,
"message": "Not Found"
}
}
{"userId":2,"exception":"[object] (Google_Service_Exception(code: 404): {
\"error\": {
\"errors\": [
{
\"domain\": \"global\",
\"reason\": \"notFound\",
\"message\": \"Not Found\"
}
],
\"code\": 404,
\"message\": \"Not Found\"
}
}
at /var/www/vendor/google/apiclient/src/Google/Http/REST.php:123)
[stacktrace]
It looks like it is calling the wrong api endpoint since a 404 is a page not found response.
Calendar ID
Just to be clear, here is where i taken the calendar id

As far as I know, service accounts do not have their own calendars, so they need to be granted access to a specific organization in order to create/read events.
If this is for an internal application (and you are an administrator for your organization), you can follow the steps under Delegating Authority to grant your service account access to the calendars for your organization:
Then, a super administrator of the G Suite domain must complete the following steps:
From your G Suite domain’s Admin console, go to Main menu menu > Security > API Controls.
In the Domain wide delegation pane, select Manage Domain Wide Delegation.
Click Add new.
In the Client ID field, enter the service account's Client ID. You can find your service account's client ID in the Service accounts page.
In the OAuth scopes (comma-delimited) field, enter the list of scopes that your application should be granted access to. For example, if your application needs domain-wide full access to the Google Drive API and the Google Calendar API, enter: https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar.
Click Authorize.
You'll also need to follow the steps to impersonate a specific user under Preparing to make an authorized API call in order to retrieve a token for a user that has access to the calendar in question.
If this is for an application intended to be installed by multiple organizations, you should consider using a managed OAuth service, like Xkit (where I work). Xkit in particular has a Google Calendar Service Account connector that abstracts away all of these steps (including a step-by-step guide for the IT admin) from you as the developer to deliver you a working access token in one API call.
As a side note, the service account example you cited uses public data from the Books API, so it does not need authorization from a user, it only needs to identify itself as an application.

Related

Debugging empty results when using the Google Calendar API library for PHP

I am building a feature for a PHP Symfony app that will update calendar events in a Google Calendar upon changes in my database on my server. The calendar belongs to the google account info#my-domain.com which I administer (domain just illustrative).
I am following the instructions for the google-api-php-client in combination with the example for using this library with the calendar API.
Since this is not a multiuser scenario but pure server-to-server communication I opted for a service account on the Google Cloud Platform.
I created a project while logged in as info#my-domain.com and a service account. I downloaded the credentials.json for that service account. I enabled the Google Calendar API.
Now to the problem:
When I use this library this way:
$client = new \Google_Client();
$client->setAuthConfig('/config/google_api_keys.json');
$client->addScope(\Google_Service_Calendar::CALENDAR);
$cal_service = new \Google_Service_Calendar($client);
$results = $cal_service->calendarList->listCalendarList()->getItems();
dump($results); //basically a fancy symfony-version of print_r()
die();
the code throws no errors but the variable $results is empty even though it should contain 3 calendars.
Yet when I use the API Explorer while being logged in as info#my-domain.com the result of the same operation (GET https://www.googleapis.com/calendar/v3/users/me/calendarList) contains the 3 items I am looking for.
Just to check, I tried instead to use the PHP library to get the events for one of the 3 calendars using its id s2b[...]e81#group.calendar.google.com, but Google responded with 404-json containing "Not found".
One caveat with the API Explorer is that it only allows for login with OAuth 2.0 so seeing what might be wrong with a service account is not possible.
Does anyone see what I might be doing wrong? Or what I should try next? Or what concept I might have misunderstood when using the API?
possible cause for your issue one
Remember that listCalendarList is not automatically populated if you want a calendar to appear in listCalendarList then your going to have to insert it there. I suggest trying to do a listCalendarList.insert and trying to add the calendar id that you gave it access to.
If that returns an error then see possible cause for issue number to
possible cause for your issue two
I assume that you have set up domain wide deligation but you forgot to set which user you are delegating as you do that by setSubject
function initializeCalendar()
{
// Use the developers console and download your service account
// credentials in JSON format. Place them in this directory or
// change the key file location if necessary.
$KEY_FILE_LOCATION = __DIR__ . '/service-account-credentials.json';
$user_to_impersonate ="user#domain.com";
// Create and configure a new client object.
$client = new Google_Client();
$client->setApplicationName("Hello Calendar");
$client->setAuthConfig($KEY_FILE_LOCATION);
$client->setScopes(['https://www.googleapis.com/auth/calendar']);
$client->setSubject($user_to_impersonate);
$service = new Google_Service_Calendar($client);
return $service;
}
Without setting up delegation the service account does not have access to any calendars.
Okay, with the help of #daimto I could come to a very simple solution:
I had to actually share the calendar owned by info#my-domain.com with the service account by adding its email address abc-123#my-project-name.iam.gserviceaccount.com in the calendar settings, just like I'd share this calendar with any other physical person like eric#gmail.com.
So my understanding of what a service account actually is was seriously flawed. I thought that it would be the co-owner of any resource (docs, spreadsheets, calendars, etc) that project's owner owned.
WRONG: info#my-domain.com owns the project my-project-name which "owns" the service account abc-123#my-project-name.iam.gserviceaccount.com and therefore the service account owns "everything".
(MORE) RIGHT: The service account acts rather like a virtual user that first has to get access to any resource that it's supposed to be working with. The access sharing can be done like you would add a friend to a resource or via an API.

PHP - Can't create new Google Calendar event using an API Key

I'm trying to insert a new event using only an API Key that I created.
The problem is that when I'm just checking available times using FreeBusy, everything works perfectly, but when I try to create and insert a new event, I get the following error:
Uncaught Google_Service_Exception: {
"error": {
"errors": [
{
"domain": "global",
"reason": "required",
"message": "Login Required",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Login Required"
}
}
In the Authorizing Requests to the Google Calendar API page it is stated that:
Your application must use OAuth 2.0 to authorize requests. No other
authorization protocols are supported. If your application uses Google
Sign-In, some aspects of authorization are handled for you.
But I can't manually authorize access to the calendar every time it is asked for.
How can I authorize requests and insert new events without having to manually authorize them?
You need to use a Service Account, if you want to perform server-to-server API requests. Please see https://developers.google.com/calendar/auth.
Generally you'd create a key, and download the JSON file that is provided. You can then call $client->setAuthConfig($jsonPath); to set up authentication. It's probably a good idea to keep the private key outside of the web root. Then set the scope using $client->addScope('https://www.googleapis.com/auth/calendar'), and instantiate the Calendar service using $service = new Google_Service_Calendar($client);. You should now be able to do your stuff.
You may also need to share the calendar with the generated e-mail address that is associated with the Service Account.

How to programmatically get the access token to send API call to Google Merchant Center (Google Shopping) with PHP?

I recently get in touch with Google merchant product to sync all my website products into Google Merchant. When I follow the structure of the API documentation https://developers.google.com/shopping-content/v2/quickstart
down to Authorization section I copy their library and copy the sample code to use. It actually work! However, when I doing testing to load that auth page it requires me to log in to the developer account for getting the access token and save it into session.
Is there any possibility that I can skip the login section to make it automatically then I can do corn system to run the sync (update products' details) hourly?
I tried to hardcore my account login API key into my code like this:
$client = new Google_Client();
$client->setApplicationName('Sample Content API application');
//add my api key here
$client->setDeveloperKey(MY_API_KEY);
$client->setClientId('YOUR_CLIENT_ID');
$client->setClientSecret('YOUR_CLIENT_SECRET');
$client->setRedirectUri('YOUR_REDIRECT_URI');
$client->setScopes('https://www.googleapis.com/auth/content');
But it doesn't workЖ шt still requires me to log in.
You want to use a service account for this.
The Google OAuth 2.0 system supports server-to-server interactions such as those between a web application and a Google service. For this scenario you need a service account, which is an account that belongs to your application instead of to an individual end user. Your application calls Google APIs on behalf of the service account, so users aren't directly involved. This scenario is sometimes called "two-legged OAuth," or "2LO." (The related term "three-legged OAuth" refers to scenarios in which your application calls Google APIs on behalf of end users, and in which user consent is sometimes required.)
Below is a working example of authentication via Service account with PHP.
PREREQUISITES
Install the "Content API for Shopping" library which you can download here. For installation see here.
Create your service account. Here you will find the complete procedure.
At this point you will have obtained your JSON file needed for authentication.
As suggested by Google:
Important: Protect the *.json key file that allows a service account
to access the Google services for which it has been authorized. It is
good practice to allow service accounts to only access one Google API
each. This is a preventative measure to mitigate the amount of data an
attacker can access in the situation that the service account’s *.json
key file is compromised.
You can now use the following function to get the access token for the API call:
// gets access token for API call to Google Merchant Center
function gets_access_token_for_API_call() {
// load the "Google APIs Client Library for PHP"
require_once ( '/path/to/google-api-php-client/vendor/autoload.php' );
// gets the JSON key of the service account
$credentialsFilePath = '/path/to/merchant-center-123456789-987654321.json';
$client = new Google_Client();
$client->setAuthConfig($credentialsFilePath);
$client->addScope( 'https://www.googleapis.com/auth/content' );
// fetches a fresh access token with a given assertion token.
$client->fetchAccessTokenWithAssertion(); // the deprecated alias: "refreshTokenWithAssertion()"
$token = $client->getAccessToken();
return $token;
}
The returned result will be an array similar to:
{
"access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
"scope": "https://www.googleapis.com/auth/content"
"token_type": "Bearer",
"expires_in": 3600
}
The code has been tested and works.

How do I authenticate with Google Calendar API? (PHP)

I have the Google Client PHP library set up via Packagist and Composer.
I have successfully followed this getting started guide to verify that I can use the Google Client. I followed the example and was able to retrieve a list of books through the Books API, but that's not what I'm actually trying to do.
I am creating a website for a company, and they want to have a form on their website that will allow users to request a meeting at a certain time, and then this will be automatically added to said company's Google Calendar.
In the Google Calendar API, I see a lot of information about using OAuth to have a user sign in and grant access to THEIR calendar, which can then be used, however this is not what I want at all. The user shouldn't have to sign in; the server should gain access to the company's Calendar and then make requests to that SPECIFIC calendar.
I have made a Public Access API Key in the Google API Console and entered it in my code, i.e.
$client = new Google_Client();
$client->setApplicationName($applicationName);
$client->setDeveloperKey($developerKey);
$calendarService = new Google_CalendarService($client);
$calendars = $calendarService->calendars;
$results = $calendars->get($calendarId);
dd($results);
I learned of these methods by looking through the source. I have found that, at least on my Google Calendar, the $calendarId is simply my gmail address (found under Calendar Settings). However, this code returns an error:
Error calling GET
https://www.googleapis.com/calendar/v3/calendars/MY_EMAIL#gmail.comkey=MY_KEY_HERE: (401) Login Required
Of course, this is all wrong. I don't want the user to need to login. I just can't find any documentation on how to authenticate my app for this scenario. Every guide I come across assumes that the user will be able to sign in so I can access THEIR calendar. But I want the server to login and access a SPECIFIC calendar that belongs to the company.

Can I access other Google User's Calendars through the Google API v3

I would like to build an application that manages appointments in Google calendar on behalf of my users when they are not logged in.
The application would be a booking service, subscribing users (our service providers) would book out the times they are not available, and customers would see a free/busy representation of the service provider's google calendar
The application would need the ability to create, delete, and read appointments.
I have gone to the Google Developer's Console and have working code that pops up the Google Permissions screen, then redirects to a Url that successfully creates a new appointment.
I can access other calendar's but only if they are are logged into Google services at that time.
$client = new \Google_Client();
$client->addScope('https://www.googleapis.com/auth/calendar');
$service = new \Google_Service_Calendar($client);
$authUrl = $client->createAuthUrl();
This works okay if the user is managing their own Calendar. However, I want to manage appointments on their behalf. This would include others parties entering appointments into the calendar - i.e. when the calendar is not logged in.
What I need is an enduring authority for application to gain enduring access to my user's calendar?
Does anyone know if this possible? Thoughts as to how I could approach the problem would be appreciated.
OAuth 2.0 service accounts are the proper method for accessing user accounts:
https://developers.google.com/accounts/docs/OAuth2ServiceAccount
You need to grant the service account client id access to the necessary Calendar API scopes:
https://developers.google.com/drive/web/delegation#delegate_domain-wide_authority_to_your_service_account
Then your service account can impersonate your users and perform calendar operations on their behalf:
https://developers.google.com/drive/web/delegation#instantiate_a_drive_service_object
These docs describe Google Drive but the process for Calendar is identical.

Categories