Grant access on spreadsheet using Google Sheets API - php

I have Google Sheets API set up and working (I can read and update existing spreadsheets if it's shared to my credentials) and I need to export some data from my website to google sheet. In order to keep users off seeing other people's sheets, I need to create a new spreadsheet when user wants to export data. I managed to create new spreadsheet like this:
public function init(){
$user = Socialite::driver('google')->stateless()->user();
$token = [
'access_token' => $user->token,
'refresh_token' => $user->refreshToken,
'expires_in' => $user->expiresIn
];
$client = new \Google_Client();
$client->setApplicationName('Sheets');
$client->setScopes([\Google_Service_Sheets::SPREADSHEETS]);
$client->setAccessType('offline');
$client->setAuthConfig('../credentials.json');
$client->setAccessToken($token);
$service = new Google_Service_Sheets($client);
$serviceDrive = new Google_Service_Drive($client);
$spreadsheet = new Google_Service_Sheets_Spreadsheet([
'properties' => [
'title' => 'Testing Sheet'
]
]);
$spreadsheet = $service->spreadsheets->create($spreadsheet, [
'fields' => 'spreadsheetId'
]);
$this->insertPermission($serviceDrive, $spreadsheet->spreadsheetId, $user->email, 'user', 'owner');
}
When I dd($spreadsheet) I can see that it's actually created and I can retrieve its id. But the thing is, if I try to open it, I get a notification that I need to get access as I don't have it. I searched for a solution a lot and tried several ways. I tried to pass a role like this:
$spreadsheet = $service->spreadsheets->create($spreadsheet, [
'fields' => 'spreadsheetId',
'role' => 'owner'
'email' => 'useremail#gmail.com'
]);
Also tried to insert permission using this method:
function insertPermission($service, $fileId, $value, $type, $role) {
$newPermission = new Google_Service_Drive_Permission();
$newPermission->setEmailAddress($value);
$newPermission->setType($type);
$newPermission->setRole($role);
try {
return $service->permissions->create($fileId, $newPermission);
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
return NULL;
}
But this method gives me an error when calling create() function which says "code": 403, "message": "Insufficient Permission: Request had insufficient authentication scopes.". Is there a way to give to authenticated user an access to newly created spreadsheet and where is my mistake?

You are trying to give permission with the Drive API
Your code only contains $client->setScopes([\Google_Service_Sheets::SPREADSHEETS]);
To use both the Sheets and the Drive API with the same client, assign to the client BOTH respective scopes

Related

Laravel 9 - Google Calendar API using google-api-php-client - Call to a member function listEvents() on null

I'm trying to enable and sync Google Calendar API in my project.
Summary:
In my current project i've multiple users and each user has a dashboard with fullcalendar.js. I've a table named events and it has these columns
Events
name
description
user_id
full_day
start
end
Users can create calendar events through a modal which i save in the database.
I want to enable the ability for the users to import their Google Calendar Events and/or 2-way sync events.
So far i've done the following:
Enabled calendar api, created OAuth Tokens for webapp (client id & secret), filled in redirect and created a google consent screen.
composer require google/apiclient in my project.
Setup Socialite Google drive
Created a controller named GoogleController and created routes for it.
OAuthGoogle method (GoogleController) with the following code:
public function OAuthGoogle()
{
$scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'openid',
'https://www.googleapis.com/auth/calendar'
];
return Socialite::driver('google')
->scopes($scopes)
->with(["access_type" => "offline", "prompt" => "consent select_account"])
->redirect();
}
After that on the redirect Method i've the following code where i store the tokens in user DB:
public function OAuthGoogleCallback()
{
$user = Socialite::with('google')->user();
$loggedUser = getAuthUser();
$loggedUser->google_access_token = Crypt::encryptString($user->token);
$loggedUser->google_refresh_token = Crypt::encryptString($user->refreshToken);
$loggedUser->save();
}
After that i'm trying to fetch calendar events and list them:
public function list()
{
$user = getAuthUser();
$token = Crypt::decryptString($user->google_access_token);
$client = new Google_Client();
$client->setAccessToken($token);
$service = new Calendar($client);
// $calendarId = 'primary';
$optParams = array(
'maxResults' => 10,
'orderBy' => 'startTime',
'singleEvents' => true,
'timeMin' => date('c'),
);
$events = $service->events->listEvents('primary');
while (true) {
foreach ($events->getItems() as $event) {
echo $event->getSummary();
}
$pageToken = $events->getNextPageToken();
if ($pageToken) {
$optParams = array('pageToken' => $pageToken);
$events = $service->events->listEvents('primary', $optParams);
} else {
break;
}
}
}
Unfortunately I get the following error when I try it:
Call to a member function listEvents() on null
thanks in advance for your help!
EDIT: i've found the problem.. i dont know how i've overlooked it..
problem was: $service = new Calendar($client);
'new Calenadar' refers to -> Google\Service\Calendar\Calendar,
however i needed Google\Service\Calendar so i've replaced it with
new \Google\Service\Calendar and it worked..

Changing the permissions of an uploaded sheets

I'm running into an issue where I have a sheet which has been uploaded using the Sheets PHP API and now I need to change the permissions of that sheet. It doesn't matter to me if the permissions are set at upload or changed later. Here's what my upload code looks like
function createSheet(){
$client = getClient();
$service = new Google_Service_Sheets($client);
$spreadsheet = new Google_Service_Sheets_Spreadsheet([
'properties' => [
'title' => "test_sheet3",
]
]);
$spreadsheet = $service->spreadsheets->create($spreadsheet, [
'fields' => 'spreadsheetId'
]);
printf("Spreadsheet ID: %s\n", $spreadsheet->spreadsheetId);
return $spreadsheet->spreadsheetID;
}
I've been poking around this google documentation but every time I try and include any of the settings in the properties JSON ex:
'properties' => [
'title' => "test_sheet3",
'type' => 'group'
]
]);
I get the error
"Invalid JSON payload received. Unknown name \"type\" at 'spreadsheet.properties': Cannot find field."
So I'm not completely sure if my syntax is incorrect, or they should be added to a different JSON attachment that isn't the properties JSON.
I would like to propose the following modification.
Modification points:
You can give the permissions using the method of "Permissions: create" in Drive API. This has already been mentioned in your question. In this case, the request parameters cannot be included in the method of "spreadsheets.create" in Sheets API. Please request it using the method of "Permissions: create" in Drive API.
When above points are reflected to your script, it becomes as follows.
Modified script:
$client = getClient();
$service = new Google_Service_Sheets($client);
$spreadsheet = new Google_Service_Sheets_Spreadsheet([
'properties' => [
'title' => "test_sheet3",
]
]);
$spreadsheet = $service->spreadsheets->create($spreadsheet, [
'fields' => 'spreadsheetId'
]);
printf("Spreadsheet ID: %s\n", $spreadsheet->spreadsheetId);
// I added below script
$drive = new Google_Service_Drive($client);
$newPermission = new Google_Service_Drive_Permission();
$newPermission->setEmailAddress('####gmail.com');
$newPermission->setType('group');
$newPermission->setRole('writer');
$res = $drive->permissions->create($spreadsheet->spreadsheetId, $newPermission);
// print_r($res);
return $spreadsheet->spreadsheetID;
Note:
In this case, as a test, I used the scope of https://www.googleapis.com/auth/drive. For this, please use the following script. And when you modified the scopes, please the file including the refresh token and reauthorize the scopes. By this, the modified scopes can be reflected to the access token. Please be careful this.
$client->setScopes(array(Google_Service_Sheets::SPREADSHEETS, Google_Service_Drive::DRIVE));
When you want to use group to type, please set the email address.
If you want to give the permissions to an user, please modify $newPermission->setType('group'); to $newPermission->setType('user'); and please use the email address.
Reference:
Permissions: create

"Insufficient privileges to complete the operation" when trying to connect to Microsoft Graph

I want to configure my Symfony4 application to read and send e-mails using the msgraph-sdk-php library.
My app would be reading and sending e-mail from a single account, whose password I don't want to expose to my app's users. Thus, I wouldn't be using OAuth for login.
My first experience was this piece of code (to retrieve mailbox user profile):
<?php
namespace App\Graph;
use Microsoft\Graph\Exception\GraphException;
use Microsoft\Graph\Graph;
use Microsoft\Graph\Model\User;
class GraphService
{
function sentTestMessage() {
$userId = "************************************";
$tenantId = "************************************";
$clientId = "************************************";
$clientSecret = "***************************";
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
],
])->getBody()->getContents());
$accessToken = $token->access_token;
$graph = new Graph();
$graph->setAccessToken($accessToken);
$user=new \stdClass();
try {
$user = $graph->createRequest("GET", "/users/".$userId)
->setReturnType(User::class)
->execute();
} catch (GraphException $e) {
$user->getGivenName=$e->getMessage();
}
return "Hello, I am $user->getGivenName() ";
}
}
But then Symfony shows me an exception page with this message:
Client error: GET https://graph.microsoft.com/v1.0/users/... resulted in a 403 Forbidden response:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the ope (truncated...)
Now the same query works when run in https://developer.microsoft.com/en-us/graph/graph-explorer with the same user logged in.
These are the permissions I gave the app:
What should I do to overcome the problem above described?
You used client credentials flow to get access token in your code, so you need application permission instead of delegated permission.

error: Undefined property while fetching data from Google analytics

I'm facing problems fetching data (Profile list to be specific) from google analytics. The credentials like client_id, secret etc are in place and it does allow user to login successfully, but I'm stuck in the next step where I need to fetch the list of profiles (list of websites). I did go through the official docs as per Google Views (Profiles): list but while I try it, I get an error:
Undefined property: App\Http\Controllers\UserController::$analytics
the detailed error:
in UserController.php line 84
at HandleExceptions->handleError('8', 'Undefined property: App\Http\Controllers\UserController::$analytics', 'C:\xampp\htdocs\Laravel Projects\testApp\app\Http\Controllers\UserController.php', '84', array('request' => object(Request), 'google_redirect_url' => 'http://localhost:8000/glogin', 'gClient' => object(Google_Client), 'google_oauthV2' => object(Google_Service_Oauth2), 'guser' => null, 'user' => object(User), 'token' => array('access_token' => 'TOKEN GOES HERE', 'token_type' => 'Bearer', 'expires_in' => '3600', 'id_token' => 'ID_TOKEN GOES HERE', 'created' => 'CREATED DATA GOES HERE'))) in UserController.php line 84
I do understand there's an error when I try to fetch the data, but then I don't really understand as to how should i be doing that. Anyone has any idea? Please help!
here's the Controller
class UserController extends Controller
{
public function googleLogin(Request $request) {
$google_redirect_url = route('glogin');
$gClient = new \Google_Client();
$gClient->setApplicationName(config('services.google.app_name'));
$gClient->setClientId(config('services.google.client_id'));
$gClient->setClientSecret(config('services.google.client_secret'));
$gClient->setRedirectUri($google_redirect_url);
$gClient->setDeveloperKey(config('services.google.api_key'));
$gClient->addScope(\Google_Service_Analytics::ANALYTICS_READONLY);
$gClient->addScope("email");
$gClient->addScope("profile");
$gClient->setAccessType("offline");
$google_oauthV2 = new \Google_Service_Oauth2($gClient);
if ($request->get('code')){
$gClient->authenticate($request->get('code'));
$request->session()->put('token', $gClient->getAccessToken());
}
if ($request->session()->get('token'))
{
$gClient->setAccessToken($request->session()->get('token'));
}
if ($gClient->getAccessToken())
{
//For logged in user, get details from google using access token
$guser = $google_oauthV2->userinfo->get();
$request->session()->put('name', $guser['name']);
if ($user =User::where('email',$guser['email'])->first())
{
}else{
//register your user with response data
return User::create([
'name' => $guser->name,
'email' => $guser->email,
]);
}
//LINE NO 84 is below:
$profiles = $this->analytics->management_profiles
->listManagementProfiles();
$accounts = $accountsObject->getItems();
return $accounts;
//return redirect()->route('user.glist');
} else
{
//For Guest user, get google login url
}
}
}
You are calling $this->analytics but nowhere do you define the property analytics for the UserController class. Not sure what is defined in the parent class Controller but I am pretty sure it is agnostic of the Analytics service object.
You need to instantiate the Google_Service_Analytics object.
// Create an authorized analytics service object.
$analytics = new Google_Service_Analytics($gclient);
See the Hello Analytics guide for details.

Authenticate access token provided by GoogleAuthUtil.getToken after sending it to php web server

I am following the docs from link below:
https://developers.google.com/+/mobile/android/sign-in#enable_server-side_api_access_for_your_app
Specifically the part that says:
If you do not require offline access, you can retrieve the access token and send it to your server over a secure connection. You can obtain the access token directly using GoogleAuthUtil.getToken() by specifying the scopes without your server's OAuth 2.0 client ID. For example:
I retrieve the access token like this:
accessToken = GoogleAuthUtil.getToken(
AuthenticatorActivity.this,
Plus.AccountApi.getAccountName(Common.mGoogleApiClient),
"oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/plus.login email"
);
After I retrieve the access token I send it to a web server, on the web server i can see that it's a valid access token by calling
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$_POST['google_access_token']
The request above returns the android apps client id, it also returns the users email correctly.
The problem is that when I try to run $client->authenticate($_POST['google_access_token']); I get an exception with the message: "invalid_grant: Incorrect token type".
To prevent getToken caching I always invalidate the token in android app:
if (accessToken != null && !accessToken.isEmpty()) {
GoogleAuthUtil.invalidateToken(AuthenticatorActivity.this, accessToken);
}
Here's the php code:
if (!isset($_POST['google_access_token'])) {
throw new Exception('missing google_access_token');
}
$client = new \Google_Client();
$client->setApplicationName("GiverHub");
$client->setClientId($this->config->item('google_client_id'));
$client->setClientSecret($this->config->item('google_client_secret'));
$client->setDeveloperKey($this->config->item('google_developer_key'));
$client->setRedirectUri($this->config->item('google_redirect_uri'));
$client->setScopes([
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/plus.me',
'email',
]);
try {
$client->authenticate($_POST['google_access_token']); // if i remove this the rest of the code below works! ...
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$_POST['google_access_token'];
$req = new \Google_Http_Request($reqUrl);
$io = $client->getIo();
$response = $io->executeRequest($req);
$response = $response[0];
$response = json_decode($response, true);
if ($response === null) {
throw new Exception('Failed to check token. response null');
}
if ($response['issued_to'] !== '466530377541-s7cfm34jpf818gbr0547pndpq9songkg.apps.googleusercontent.com') {
throw new Exception('Invalid access token. issued to wrong client id: '. print_r($response, true));
}
if (!isset($response['user_id'])) {
throw new Exception('Missing user_id');
}
if (!isset($response['email'])) {
throw new Exception('Missing email');
}
/** #var \Entity\User $user */
$user = Common::create_member_google([
'id' => $response['user_id'],
'email' => $response['email'],
'given_name' => '',
'family_name' => '',
]);
$user->login($this->session);
if ($user instanceof \Entity\User) {
echo json_encode( [ 'success' => true, 'user' => $user ] );
} else {
echo json_encode( [ 'success' => false, 'msg' => $user ] );
}
} catch(Exception $e) {
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
}
The above code works if i remove the $client->authenticate(); line ... The problem is that I can't get the given_name / family_name etc .. only email / google_user_id from the tokeninfo ...
Any thoughts about why the key works for tokeninfo but not for authenticate?
I have tried many different variations of the scopes .. both on the server side and the android side ..
The $client->authenticate() method doesn't quite do what you're trying to do. It takes a one-time code from an earlier OAuth transaction and exchanges it for the access token. In your case - you're saying you already have the access token.
You should be able to call $client->setAccessToken() to set the token instead, so it may look something like
$client->setAccessToken($_POST['google_access_token']);
This is the solution I came up with after user158443 suggested I use $client->setAccessToken();
// first json_encode the access token before sending it to $client->setAccessToken();
$json_encoded_access_token = json_encode([
'access_token' => $_POST['google_access_token'],
'created' => time(), // make up values for these.. otherwise the client thinks the token has expired..
'expires_in' => time()+60 // made up a value in the future...
]);
// and then set it
$client->setAccessToken($json_encoded_access_token);
// and then get userinfo or whatever you want from google api !! :)
$oauth2 = new \Google_Service_Oauth2($client);
$user_info = $oauth2->userinfo->get();
NOTE: it's probably not smart to "emulate" the expires_in and created that i just did if you are in production ... You should probably call tokeninfo first and get the expires time from there...
NOTE: I still have no idea how to get a refresh token for this... but I don't need one for my use case..

Categories