Google AppEngine + PHP + Google Cloud Datastore: receiving Unauthorized - php

I have a PHP application running at Google App Engine and I want to use the App Engine's datastore in it.
I am using the google-api-php-client, and at the Google Cloud Console I've enabled the Google Cloud Datastore API, and registered a new app, downloading the private certificate.
For the authentication, I'm using the following code (xxxx has the real values):
const SERVICE_ACCOUNT_NAME = 'xxxx#developer.gserviceaccount.com';
const KEY_FILE = 'secure/privatekey.p12';
$client = new Google_Client();
$key = file_get_contents ( KEY_FILE );
$client->setAssertionCredentials (
new Google_AssertionCredentials (
SERVICE_ACCOUNT_NAME,
array (
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/datastore'
),
$key
)
);
$service = new Google_DatastoreService ( $client );
$datasets = $service->datasets;
Whichever operation I use in $datasets, like $datasets->lookup, I receive an exception stating that the operation is Unauthorized:
Uncaught exception 'Google_ServiceException' with message 'Error calling POST https://www.googleapis.com/datastore/v1beta1/datasets/<my-appengine-id>/lookup: (403) Unauthorized.'
What might be wrong?

I believe you are hitting the following limitation: service account authorization doesn't work with domain-restricted App Engine application.
So you can either comment on the issue to get your application whitelisted, keep in mind that it can allow account outside of your domain to auth with your application using OAuth depending on how your application is built.

Related

How to solve authentication error when accessing google pay api for passes?

I've been following the steps here and here to try to use a service account key file to access the Google pay api and insert a LoyaltyClass. The Google Pay library suggests using version 1.1.1 of the Google api client library, which is what I've done here:
$client = new Google_Client();
$client->addScope('https://www.googleapis.com/auth/wallet_object.issuer');
$client->setApplicationName("Test");
$client->setDeveloperKey("My service account key");
$test_class = new LoyaltyClass;
$service = new Google_Service_Walletobjects($client);
$wallet_object = $test_class->create_wallet_class();
$service->loyaltyclass->insert($wallet_object);
And I get this error:
PHP Fatal error: Uncaught Google_Service_Exception: Error calling POST https://www.googleapis.com/walletobjects/v1/loyaltyClass?key=2ee78c76d17cabcdce22c4e89af27ab73ad694a4: (401) 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. in /var/www/html/google-api-php-client/src/Google/Http/REST.php:76
The cloud api console logs that I've reached the api with the correct service account, but doesn't log any requests. The link they suggest directs me to prompt a user to login, but I want to access this api without input from a user, just my service account. Is there a way to do this?
What are you supplying to setDeveloperKey?
The documentation uses setAuthConfigFile instead of setDeveloperKey:
$client = new Google_Client();
$client->setApplicationName('Wallet Objects App');
$client->setScopes(array('https://www.googleapis.com/auth/wallet_object.issuer'));
$client->setAuthConfigFile('/example/path/to/privatekey.json');
There's a GitHub sample project that initializes the Google_Client class which uses the following code:
$this->client = new Google_Client();
// do OAuth2.0 via service account file.
// See https://developers.google.com/api-client-library/php/auth/service-accounts#authorizingrequests
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . SERVICE_ACCOUNT_FILE); // for Google_Client() initialization for server-to-server
$this->client->useApplicationDefaultCredentials();
// Set application name.
$this->client->setApplicationName(APPLICATION_NAME);
// Set Api scopes.
$this->client->setScopes(array(SCOPES));
SEVICE_ACCOUNT_FILE is defined in Config.php.
If you'd like a step by step tutorial for integrating with the Passes API, there's a codelab that you can follow: https://codelabs.developers.google.com/passes-loyaltyapi
It's written for NodeJS, but most of the concepts should still apply to PHP.
EDIT: Updated to refer to setAuthConfigFile from the Google Pay developer site.
EDIT: Updated to include PHP sample code from GitHub

Authless access to google drive api

Trying to list my files from personal account using google/apiclient and cannot succeed. My steps
Created service account
Enabled domain delegation
Downloaded the keys (for the service account, not the client delegate)
Using google api client to connect (code below)
Initialisation and listing
private function _initClient(string $keyLocation)
{
if (empty($keyLocation) || !file_exists($keyLocation))
{
throw new \Exception("Missing google certificate file");
}
$client = new \Google_Client();
$client->setApplicationName('My App');
$client->useApplicationDefaultCredentials();
$client->setSubject("my.email#gmail.com");
$client->setScopes([
'https://www.googleapis.com/auth/drive',
]);
return $client;
}
public function listDirectories()
{
$drive = new \Google_Service_Drive($this->client);
$files = $drive->files->listFiles([
'corpus' => 'user',
'spaces' => 'drive'
]);
var_dump($files);
}
require_once 'vendor/autoload.php';
$key = __DIR__.DIRECTORY_SEPARATOR.'client_id.json';
putenv('GOOGLE_APPLICATION_CREDENTIALS='.$key);
$t = new Myclass($key);
$t->listDirectories();
In response I get :
Uncaught Google_Service_Exception: {
"error": "unauthorized_client",
"error_description": "Client is unauthorized to retrieve access tokens using this method."
}
So the main question is what I am missing? where I can pre-authorized my delegated account ? Or there is another way to communicate with Drive Api without user confirmation?
You cannot enable domain wide delegation(DWD) for #gmail.com accounts because you are not the owner of the domain gmail.com. DWD is only possible for G Suite accounts. For gmail.com accounts, you need to take another approach. I strongly recommend you to go over this documentation for more details.
In summary, I don't think is possible to do this without user consent. I hope this information helps.

Setup google-api-php-client to comunicate with custom google app engine API endpoint

I'm trying to configure the google-api-php-client library in my project.
I've already created a custom google app engine project that consists in a cloud endpoint. The project is called 'set-core', the service is called 'vrp API', version 'v1' and the method vrp.vrp.getSolution().
Now in my PHP code i'm following this example:
https://developers.google.com/api-client-library/php/start/get_started#building-and-calling-a-service
The problem is that in this example there's no mention how to connect to any custom service, outside Google's ones.
My PHP code is:
$client = new Google_Client();
$client->setApplicationName("set-core");
$client->setDeveloperKey("AIzaSyByd8cRJNGYC4szFLbr3**************");
$client->isAppEngine(true);
$service = new Google_Service_Appengine_Service($client);
$results = $service->vrp->vrp.vrp.getSolution($stringVehicles, $stringServices, $stringDepot);
Unfortunately, on the last line, PHP warns me:
Notice: Trying to get property of non-object (I assume it's $service).
The problem is that I don't really know how to set up all the client's params and which Service type use.
You are going to want to create an authorized HTTP client and then request your API endpoint directly with it. The AppEngine service classes you're manipulating above are not meant for this use case. Something like this should work:
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$httpClient = $client->authorize();
$response = $httpClient->request('GET', 'https://myapp.appspot.com/vrp/getSolution');
The $httpClient class is an instance of GuzzleHttp\Client, but with your Google authentication already added to it. See the documentation for making a request with Guzzle.
I hope this helps!

Is it possible to access the Google Spreadsheets API via an Google API Service account? How to do so in PHP?

I am successfully using the Google PHP API with an service account to manipulate Google CloudDNS records like this:
$permisson = 'https://www.googleapis.com/auth/ndev.clouddns.readwrite';
$client = new Google_Client();
$client->setApplicationName("foo");
$credentials = new Google_Auth_AssertionCredentials(
$googleApiServiceAccount,
array($permission),
file_get_contents($googleApiKeyFile)
);
$client->setAssertionCredentials($credentials);
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($credentials);
}
$accessToken = $client->getAccessToken();
[..]
Is it possible to use a similar scheme for Google Apps Spreadsheets access? What to use as $permission and what kind of API do i have to enable in the Google Developer Console?
Please note: I am fully aware of the fact that it is easy to access the API via oAuth2 - i am looking for a way to access it via a service account.
Regarding the permission part:
$permissions = array(
"https://spreadsheets.google.com/feeds",
"https://docs.google.com/feeds"
);
$credentials = new Google_Auth_AssertionCredentials(
$googleApiServiceAccount,
$permissions,
file_get_contents($googleApiKeyFile)
);
And the Google API Console Service Account needs to be added as collaborator to the spreadsheet you want to access! You can create/see
the service account email addresses here:
https://console.developers.google.com/project/YOUR-PROJECT-NAME/permissions/projectpermissions
This is the service to use:
$service = new \Google\Spreadsheet\SpreadsheetService();

Save to local Google Datastore with PHP

The following code inserts an entity into the remote Google Datastore:
require_once('google-api-php-client/src/Google_Client.php');
require_once('google-api-php-client/src/contrib/Google_DatastoreService.php');
$client = new Google_Client();
$client -> setApplicationName("myApp");
$serviceAccount = 'myServiceAccount#developer.gserviceaccount.com';
$key = file_get_contents('super/secret/encryptedKey-privatekey.p12');
$client -> setAssertionCredentials(new Google_AssertionCredentials(
$serviceAccount,
array('https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/datastore'), $key));
$datastore = new Google_DatastoreService($client);
$path = new Google_KeyPathElement();
$path -> setName('testName');
$path -> setKind('testKind');
$key = new Google_Key();
$key -> setPath(array($path));
$entities = new Google_Entity();
$entities -> setKey($key);
$mutation = new Google_Mutation();
$mutation -> setInsert(array($entities));
$blindWrite = new Google_BlindWriteRequest();
$blindWrite -> setMutation($mutation);
$datastore -> datasets -> blindWrite('myApp', $blindWrite);
Now this is not really ideal for local development, so I changed the 'basePath' in google-api-php-client/src/config.php from
'basePath' => 'https://www.googleapis.com',
to
'basePath' => 'http://localhost:53290',
which is my API server according to the app launcher log.
Unfortunately, this does not work and any requests to the local API server result in the following error:
Fatal error: Uncaught exception 'Google_ServiceException' with message 'Error calling POST http://localhost:53290/datastore/v1beta1/datasets/myApp/blindWrite: (400) HTTP requires CRLF terminators' in ***\google-api-php-client\src\io\Google_REST.php:66
However, accessing http://localhost:53290 works and returns:
{app_id: dev~myApp, rtok: '0'}
Does anybody know what's causing this error? The request is exactly the same and I know that the local datastore is working in other languages.
Caveat: I have not developed in PHP on GAE, only in python and java.
According to this answer the PHP runtime currently cannot access the GAE-datastore; instead it uses Google Cloud Datastore which is accessed differently. (Just wanted to clarify this, since you tagged your question with gae-datastore)
AFIAK, the GAE development server does not support Google Cloud Datastore (which you are using via Google_DatastoreService.php). In order to simulate Google Cloud Datastore locally you will need to run the Google Cloud Datastore development server. -- However, if you can even use this depends on whether the PHP client uses their protocal buffer or their JSON API. Only the former is supported. See note on that page. So you might be stuck at this point.
Assuming you can use the local devserver:
I don't know what basepath in google-api-php/client/src/config.php is referring to and if changing it is the way to go when you are running the Google Cloud Datastore local devserver. The docs for the devserver state that you should modify the environment variables DATASTORE_HOST and DATASTORE_DATASET.
(BTW: I believe the API server entry in your app launcher log refers to Google Cloud Endpoints which aren't related to your problem.)
This is the easiest workaround if someone wants to use a different Cloud Datastore for development and production using PHP:
Create two applications:
myApp
myApp-dev
Activate the Datastore API for both applications. I was a little surprised but you can access a datastore from any application as long as you have the correct service-account-id and private key.
Implement a simple switch:
const DATASTORE_APP_NAME_PRODUCTION = 'myApp';
const DATASTORE_SERVICE_ACCOUNT_NAME_PRODUCTION =
'production-service-account-id#developer.gserviceaccount.com';
const DATASTORE_APP_NAME_DEVELOPMENT = 'myApp-dev';
const DATASTORE_SERVICE_ACCOUNT_NAME_DEVELOPMENT =
'dev-service-account-id#developer.gserviceaccount.com';
if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'],
'Google App Engine') !== false) {
$runMode = 'PRODUCTION';
} else {
$runMode = 'DEVELOPMENT';
}
Then do something like this when you want access the Cloud Datastore in your application:
$serviceAccount = constant('DATASTORE_SERVICE_ACCOUNT_NAME_'.$runMode);
$appName = constant('DATASTORE_APP_NAME_'.$runMode);
$key = file_get_contents('secret/path/'.$runMode.'-privatekey.p12');
$client -> setAssertionCredentials(
new Google_AssertionCredentials($serviceAccount,
array('https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/datastore'), $key));
I have recently created a library that can handle this: https://github.com/pwhelan/datachore. The actual magic is in: https://github.com/pwhelan/datachore/blob/master/src/Datastore/GoogleRemoteApi.php.
The code works just as well once it is deployed.

Categories