Google API not always returning profile data - google-api

Using django-allauth, I am allowing users to log in through their Google profiles using the all of the following settings:
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
'https://www.googleapis.com/auth/gmail.labels',
'https://www.googleapis.com/auth/gmail.modify'
],
'AUTH_PARAMS': {
'access_type': 'offline',
}
}
}
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
'profile',
'email',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
]
SOCIALACCOUNT_QUERY_EMAIL = True
SOCIALACCOUNT_EMAIL_VERIFICATION = "none"
SOCIALACCOUNT_EMAIL_REQUIRED = False
SOCIALACCOUNT_STORE_TOKENS = True
SOCIALACCOUNT_AUTO_SIGNUP = True
For some reason, the extra data I am receiving is inconsistent. Sometimes, I receive all of the profile data. Sometimes, I don't get the user's name. Sometimes I don't even get the user's email. The only thing I seem to consistently get is an id and a link to a avatar photo.
The accounts I am testing are all my own and I am able to verify that they all have their publicly available names saved correctly. Google seems to only sometimes return the full set of permissible data from the profile scope. I should note that the data it returns is consistent per each account. I never get different data for an account. Has anyone had an experience like this?

Related

Google Analytics API V4 does not work with GA4 account

Is it possible to get data from new Google Analytics (GA4) accounts through API V4? It always returns the following error message:
{ "error": { "code": 403, "message": "User does not have sufficient permissions for this profile.", "errors": [ { "message": "User does not have sufficient permissions for this profile.", "domain": "global", "reason": "forbidden" } ], "status": "PERMISSION_DENIED" } }
I can do it perfectly on UA accounts.
Is there any API (web server request - OAuth) specific to this new account type?
property id
Here is the code used (PHP):
require_once __DIR__ . '/vendor/autoload.php';
session_start();
$client = new Google_Client();
$client->setAuthConfig(__DIR__ . '/FILE.json');
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->setAccessToken($_SESSION['access_token']);
$analytics = new Google_Service_AnalyticsReporting($client);
$response = getReport($analytics);
printResults($response);
function getReport($analytics){
$dateRange = new Google_Service_AnalyticsReporting_DateRange();
$dateRange->setStartDate("7daysAgo");
$dateRange->setEndDate("today");
$sessions = new Google_Service_AnalyticsReporting_Metric();
$sessions->setExpression("name");
$sessions->setAlias("sessions");
$request = new Google_Service_AnalyticsReporting_ReportRequest();
$request->setViewId('307566943');
$request->setDateRanges($dateRange);
$request->setMetrics(array($sessions));
$body = new Google_Service_AnalyticsReporting_GetReportsRequest();
$body->setReportRequests( array( $request) );
return $analytics->reports->batchGet( $body );
}
User does not have sufficient permissions for this profile
Means that the user you have authenticated your application with. Does not have permission to access the Google analytics view you are trying to extract data from.
The issue can also be caused if you are trying to use the Google analytics reporting api with a Google analytics GA4 account. As GA4 property id are not the same as UA view ids. The system gets confused and assumes you just dont access.
The solution is to authenticate the app with a user that has access to that view or grant the user access. And to check that you are using the correct api for the type of google analytics you are trying to access.
UA vs GA4
Also remember that to extract date from a GA4 account you need to use the Google analytics data api. If you have extracted data from UA accounts you have been using the Google analytics reporting api. These are two completely diffrent APIs with diffrent methods.
Google analytics data api quick start
require 'vendor/autoload.php';
use Google\Analytics\Data\V1beta\BetaAnalyticsDataClient;
use Google\Analytics\Data\V1beta\DateRange;
use Google\Analytics\Data\V1beta\Dimension;
use Google\Analytics\Data\V1beta\Metric;
/**
* TODO(developer): Replace this variable with your Google Analytics 4
* property ID before running the sample.
*/
$property_id = 'YOUR-GA4-PROPERTY-ID';
// Using a default constructor instructs the client to use the credentials
// specified in GOOGLE_APPLICATION_CREDENTIALS environment variable.
$client = new BetaAnalyticsDataClient();
// Make an API call.
$response = $client->runReport([
'property' => 'properties/' . $property_id,
'dateRanges' => [
new DateRange([
'start_date' => '2020-03-31',
'end_date' => 'today',
]),
],
'dimensions' => [new Dimension(
[
'name' => 'city',
]
),
],
'metrics' => [new Metric(
[
'name' => 'activeUsers',
]
)
]
]);
// Print results of an API call.
print 'Report result: ' . PHP_EOL;
foreach ($response->getRows() as $row) {
print $row->getDimensionValues()[0]->getValue()
. ' ' . $row->getMetricValues()[0]->getValue() . PHP_EOL;
}

Google contacts are not showing by using Google People API

Since Google is deprecating Google contacts API and instead advising us to use Google People API to add/create/delete contacts. I was able to create, get Google contacts, Sample code is below:
const { google } = require("googleapis")
const path = require("path")
const keyFile = path.join(__dirname, "serviceAccCredentials.json")
const scopes = [
"https://www.googleapis.com/auth/contacts",
"https://www.googleapis.com/auth/contacts.readonly"
]
function log(arg) {
console.log(JSON.stringify(arg, null, 4))
}
const run = async () => {
try {
const { people, contactGroups } = google.people({
version: "v1",
auth: await google.auth.getClient({
keyFile,
scopes
})
})
const createContact = await people.createContact(
{
requestBody: {
names: [
{
givenName: "Yacov 3",
familyName: "110$"
}
],
"memberships": [
{
"contactGroupMembership": {
contactGroupId: 'myContacts'
// "contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
}
)
log(createContact.data)
const afterResponse = await people.connections.list({
resourceName: "people/me",
personFields: "names",
})
log(afterResponse.data)
} catch (e) {
console.log(e)
}
}
run()
Problem is that i don't see the contacts created with the service account under the Google contacts. Normally the service account is created for the G-suit user, under the G-suit domain wide delegation settings, i added the project id with scope as well. Also People API is enabled in the service account.
Further, In the playground area of Google's official documentation when i tried to create the a Google contact, it worked. The request from there API explorer / playground looks like this
const createContact = await people.createContact({
"personFields": "names",
"sources": [
"READ_SOURCE_TYPE_CONTACT"
],
"prettyPrint": true,
"alt": "json",
"resource": {
"names": [
{
"givenName": "test 2",
"familyName": "playground"
}
],
"memberships": [
{
"contactGroupMembership": {
"contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
})
Strangely, all these properties like contactGroupResourceName, personFields, sources, alt, prettyPrint doesn't exists.
can anyone really tell me what is going on.
PS: i can not and don't want to use OAuth2 since the application is going to be server to server communication, wouldn't involve any human consent. Thanks
Issue:
You might have enabled domain-wide delegation for your service account, but you are not using it to impersonate a regular user.
The purpose of domain-wide delegation is for the service account to act on behalf of any user in the domain, but in order to do that, you have to specify which user you want the service account to impersonate.
Otherwise, the service account will access its own resources (its Contacts, its Drive, its Calendar, etc.) not the resources of a regular account. Therefore, you'll not see the created contacts if you access Contacts UI with a regular account, since contacts were not created for this account.
Solution:
You need to impersonate the account for which you want to create contacts.
In order to do that, since you're using Node's getClient(), you should specify the email address of the account you want to impersonate, as shown here:
auth.subject = "email-address-to-impersonate";
Update:
In this case, you could do the following:
let auth = await google.auth.getClient({
keyFile,
scopes
});
auth.subject = "email-address-to-impersonate";
const { people, contactGroups } = google.people({
version: "v1",
auth: auth
})
Reference:
Google Auth Library: Node.js Client

How to use a nested request with insights in the Facebook API

I am trying to include insights in a single call to Facebook ads API using Ruby. I researched this and got the following call:
params = {
'time_range': {
'since': '2019-08-01',
'until': '2019-08-31',
}
}
ad_account.campaigns(
fields: [
'adsets{
id,
insights{spend, impressions, clicks, ctr, cpc},
adcreatives{id, object_story_spec, image_url, object_type}
}'
],
params: params
).to_json
My response:
[
{
"adsets"=>{
"data"=>[
{
"id"=>"xxxxxxxxx",
"adcreatives"=>{
"data"=>
[
{
"id"=>"xxxxxxxxxxxxxx",
"object_story_spec"=>{
...
},
"image_url"=> "https://scontent.xx.fbcdn.net/v/xxxxxx",
"object_type"=>"SHARE"
}
],
"paging"=>{
"cursors"=>{
"before"=>"xxxxxx",
"after"=>"xxxxxxx"
}
}
}
}
],
...
So by levels, I am able to get all the campaigns, then the adsets inside them, and the adcreatives inside the adsets, but not the insights.
Am I doing something wrong? Does anyone have any experience with this?
So, surprisingly, Facebook DOES return insights with my call. Sometimes. No explanation why, but I tried with two accounts and got on one account insights and the other not.
Maybe it has something to do with the insights returned with this call have a shorter lifespan, so you can't get insights for older ads. Not sure. Not going to read through the million of pages.
My solution, for whoever is interested.
I made a call to adaccount insights directly, and specified the level as 'ad' so ir will return insights for all the account ads. I also requested the 'ad_id'. Later I was able to match the results returned with this call to the previous call I mentioned by comparing the ad ID, and merge the results.
ad_account.insights(
fields: ['ad_id', 'spend', 'impressions', 'clicks', 'ctr', 'cpc'],
level: 'ad',
time_range: {
since: date_since,
until: date_until
}
).to_json

Drive Api v3: Changes watch request TeamDriveId being ignored?

I'm trying to specify a Team Drive to watch according to the docs here: https://developers.google.com/drive/api/v3/reference/changes/watch
teamDriveId | string | The Team Drive from which changes will be returned. If specified the change IDs will be reflective of the Team Drive; use the combined Team Drive ID and change ID as an identifier.
(Side note teamDriveId appears to be deprecated in favour of driveId, but the docs don't reflect that)
I read that as saying if you supply an ID only changes related to that folder (File) and its children will cause a push notification, but I still get notifications for any changes in that user's Drive. Am I reading the docs incorrectly?
I originally posted the question to the DotNet client library repo as I'm doing this in C#. Jon Skeet suggested that IncludeItemsFromAllDrives should be false, which seems perfectly logical and I had tried that first because of that. However, that results in the following error being returned from the API endpoint:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "includeTeamDriveItemsRequired",
"message": "The includeItemsFromAllDrives parameter must be set to true when driveId is specified or corpora contains drive or allDrives."
}
],
"code": 403,
"message": "The includeItemsFromAllDrives parameter must be set to true when driveId is specified or corpora contains drive or allDrives."
}
}
I've tried several variations of the following properties to see if I can restrict it down to a Team Drive, but have been unsuccessful:
SupportsTeamDrives
SupportsAllDrives
IncludeItemsFromAllDrives
RestrictToMyDrive
Clarification would be very welcome.
Edit:
Code as requested, it's in C#, but the issue is likely to be the main API according to Jon Skeet:
Channel channel = new Channel()
{
Id = Guid.NewGuid().ToString(),
Type = "web_hook",
Address = [address],
Token = [token], //max 256 chars
Expiration = new DateTimeOffset(DateTime.Now.AddMinutes(10).ToUnixTimeMilliseconds(),
};
var watchRequest = _driveService.Changes.Watch(channel, startPageToken);
//watchRequest.SupportsTeamDrives = true;
watchRequest.SupportsAllDrives = true;
//watchRequest.TeamDriveId = rootFolderId;
//watchRequest.IncludeTeamDriveItems = true;
watchRequest.IncludeItemsFromAllDrives = true;
watchRequest.DriveId = rootFolderId;
watchRequest.RestrictToMyDrive = true;
channel = await watchRequest.ExecuteAsync();
I've tried several variations as the commented out code shows.

Can't write acl rules to primary calendar in google service account

So I have set up a google service account for one of my apps. My intention is to keep a google calendar associated with the admin portal that all of the admins can post events to. I have got the JWT auth working I can post events to the calendar and perform other API actions. However, for some reason I cannot change the access control rules on the primary calendar. It is initialized with a single acl rule (role: owner, scope: {type: user, value: service_account_id}), and when I try to add public read access (role: reader, scope: {type: default}) like so:
POST https://www.googleapis.com/calendar/v3/calendars/primary/acl
Authorization: Bearer my_jwt_here
{
"role":"reader",
"scope":{
"type":"default"
}
}
I get the following error:
{
"error": {
"errors": [
{
"domain": "calendar",
"reason": "cannotRemoveLastCalendarOwnerFromAcl",
"message": "Cannot remove the last owner of a calendar from the access control list."
}
],
"code": 403,
"message": "Cannot remove the last owner of a calendar from the access control list."
}
}
This doesn't make any sense to me because this request shouldn't be trying to remove any access control rules. When I create a secondary calendar and do this I have no issues. When I do this with the primary calendar of my personal google account I have no issues. Is this some behavior specific to service accounts that I am not familiar with or what? I could settle for using a non-primary calendar but it bothers me that this isn't working. Any advice is appreciated.
so I found a weird work around for this issue and im posting here because I could not find SQUAT to help resolve this so hopefully this saves others some hassle.
I will also post some common problems I found when creating a organization-wide calendar (whether this is your use case or not I believe these tips will be helpful) - Jump to the bottom of the solution to this particular error.
First I needed to set up authentication with google calendar:
const { google } = require("googleapis");
const calendar = google.calendar("v3");
const scopes = [
"https://www.googleapis.com/auth/admin.directory.resource.calendar",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/admin.directory.user",
];
const path = require("path");
const key = require(path.join(__dirname, "google-cal-api.json"));
I created a service account and then allowed it domain wide delegation with the above listed scopes; then downloaded the key. Now if you want to do actions like create calendar events FOR users within this domain what you have to do is generate a JWT token that 'impersonates' the user whos calendar you wish to interact with; like so
const generateInpersonationKey = (email) => {
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
scopes,
email
);
return jwtClient;
};
To set up a JWT client for the service account itself (and so you can create a calendar people can subscribe to; in our case it was a google calendar to show whos on leave within the workplace; so a calendar that has ALL that people can subscribe and toggle on/off was ideal) you just replace the email with 'null' and it defaults to itself, instead of 'impersonating' someone within the domain wide org.
Creating events are simple, follow the google cal api docs, depending on the auth token will depend on where the calendar is generated
JUMP HERE FOR THE IMMEDIATE SOLUTION TO THE ABOVE
For resolving the issue you pointed out; What I did was set my personal accounts email as an owner of this service accounts calendar with the following NodeJS code:
var request = await calendar.acl.insert({
auth,
calendarId: "primary",
resource: {
role: "owner",
scope: {
type: "user",
value: "callum#orgdomain.com",
},
},
});
I set myself as an owner, then I went to Google Calendar API > Patch (Try Me) filled in the calendarId as the service account with the calendar im trying to restrict; and then rule ID would be the gsuite domain domain:orgdomain.com The body should be
{
"role": "reader",
"scope": {
"type": "domain",
"value": "orgdomain.com"
}
}
And thats how I was able to restrict people within our gsuite domain from deleting or editing custom calendar events. This solution is coming from the perspective of someone who originally inserted the domain ACL as
var request = await calendar.acl.insert({
auth,
calendarId: "primary",
resource: {
role: "owner",
scope: { type: "domain", value: "orgdomain.com" },
},
});
Because adding it as a 'reader' like this messes with the service account ownership and wont allow anything but owner
Hope this has been helpful
Callum

Resources