Javascript: Google Calender + gapi: service account - javascript

My app makes use of Firebase to log users in, and they have access to a calendar, which is especially created as a single common calendar to add and remove reservations.
The app has, so to speak, its own gmail address for this purpose. The app is accessed via the google API (gapi client).
In order for the users to be able to interact with the calendar, I have found out that I can only use the service account of the calendar / app.
So far I managed to:
create a service key for the service account,
share the calendar with the service account "dummy" user,
use that service key (certificate) to create a JWT token (using jsrsasign library),
make a POST request to get an access token for gapi,
initialise the gapi auth and client, and have access to the calendar via gapi
Now when I get to the point of retrieving the google Calendar events, I do get a successful response, but the array of events is empty, even though there are test events available in the shared calendar.
The response looks like this:
{
"kind": "calendar#events",
"etag": "\"pqef3g4h5j6j0g\"",
"summary": "my_app_email#appspot.gserviceaccount.com",
"updated": "2019-01-15T21:14:05.029Z",
"timeZone": "UTC",
"accessRole": "owner",
"defaultReminders": [],
"items": []
}
There are a few topics on Stackoverflow regarding this, but none of them have helpful information, or they are for Pythin / PHP.
I am hoping someone can give advice with this for Javascript...

I resolved this... The problem was in the gapi request, when fetching the events.
I was using the wrong calendarId. I had its value set to the default 'primary', but the actual calendarId to be used, can be found under the Google Calendar Settings >> Integrate Calendar. In my settings, the calendarId was the associated account's email address.
So the gapi request looks like this:
const fetchTimeLimit = new Date(2019, 0, 1).toISOString();
let events = await gapi.client.calendar.events.list({
calendarId: 'calendar_email#gmail.com',
timeMin: fetchTimeLimit,
showDeleted: false,
singleEvents: true,
maxResults: 300,
orderBy: 'startTime'
})
.then(response => { ........etc

Related

Google Calendar API not returning attendees when listing events

When retrieving events using the Google Calendar Api v3 the attendees array is not included in the results. I'm authenticating with a service account, so there should be no access restrictions on individual fields.
My request looks like this (authentication code omitted for brevity):
async function fetchEvents() {
const calendar = google.calendar('v3')
google.options({ auth: await auth() })
return await calendar.events.list({
calendarId:
'<the-actual-calendar-id>#group.calendar.google.com',
fields:
'items(summary,description,attendees)',
maxAttendees: 99,
timeMin: new Date().toISOString(),
maxResults: 5,
singleEvents: true,
orderBy: 'startTime'
})
}
Omitting the fields parameter entirely also doesn't include the attendees although the field is part of the Events Resource.
Likewise, omitting maxAttendees also doesn't change anything.
Following the htmlLink returned from the API, I can verify that a particular event has a list of attendees.
Sounds like you have not set up delegation properly. When you delegate to the user in your workspace domain. That user has access on the domain for the-actual-calendar-id>#group.calendar.google.com
Then you init impersonation with the with_subject .
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = '/path/to/service.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject('user#example.org')
As pointed out in DalmTo's answer an event's attendees is a privileged field that can't be retrieved with a service account unless it is used to impersonate the calendar's owner.
Setting up a service account to impersonate a user
Enable domain-wide delegation for the service account in the Admin Console. The OAuth scope required is https://www.googleapis.com/auth/calendar.events.
Use google.auth.JWT to impersonate the user (from this GitHub issue):
const auth = google.auth.JWT({
subject: 'user#your-workspace-domain.com',
keyFile: '/path/to/service-account-keyfile.json',
scopes: ['https://www.googleapis.com/auth/calendar.events']
})
const calendar = google.calendar({ version: 'v3', auth })
const { data } = await calendar.events.list({ ... })

Google Calendar API - Request had insufficient authentication scopes

My app is displaying list of events from users gmail "primary" calendar (and creates/deletes events). App works fine with one of my gmail accounts, when I try to sign in with other gmail accounts I get an error when fetching events:
GET 403, and in response => Request had insufficient authentication scopes.
My code for loading calendar:
window.gapi.load("client:auth2", () => {
console.log("Client loaded!!");
window.gapi.client.init({
apiKey: "MY_API_KEY",
clientId:
"MY_CLIENT_ID",
discoveryDocs: [
"https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest",
],
scope: "https://www.googleapis.com/auth/calendar.events",
});
window.gapi.client.load("calendar", "v3", () => {
console.log("Calendar loaded!!");
// List events after calendar is loaded
listUpcomingEvents("week");
});
});
And in my listUpcomingEvents() function I fetch events with pure js(no backend):
window.gapi.client.calendar.events
.list({
calendarId: "primary",
timeMin: start.toISOString(),
timeMax: end,
showDeleted: false,
singleEvents: true,
maxResults: 100,
orderBy: "startTime",
})
.then(function (response) {
var events = response.result.items;
console.log("Events:", events);
// Set events
setEvents([...events]);
setEventsFilter(events_filter);
});
I have tried setting scopes in my Google Cloud Platform (oauth consent screen), switched to other scopes or add more than scope but nothing works. I have even tried to make calendar public and bunch of other things i found on stackoverflow.
Request had insufficient authentication scopes.
Means that the user has authenticated with one scope but the method you are using requires another.
The method Events.list requires one of the following scopes
Now the code you have posted shows scope: "https://www.googleapis.com/auth/calendar.events", which is one of the scopes that should give you access to list events.
The issue is that when you ran that code the consent screen popped up and the user authorized your application. You then changed the scope to https://www.googleapis.com/auth/calendar.events and ran your code again. But the new consent screen didn't popup because the user is currently authenticated already.
You need to fore your app to request authorization of the user again, at that time the new scope will show up and if the user consents to it you will be able to use that method.
The easiest way to do this is to go to the users google account and revoke the access that was granted to your application. Google account
Or as your application is JavaScript you should be able to wait an hour and your access should expire and the user will be asked to consent again.

Google Calendar API - Push notifications not working

I'm using the Google Calendar API and am trying to receive push notifications when a calendar event is triggered https://developers.google.com/calendar/v3/push
I think everything is setup correctly...
gapi.client.calendar.events.watch({
calendarId: 'primary',
resource: {
id: uuid,
type: 'web_hook',
address: window.location.href,
},
}, (err, response) => {
if (err) {
console.log('err:', err);
} else {
console.log('response:', response);
}
}).then((res) => {
console.log('res:', res);
});
But I guess not. I get a 200 response when I call the above code
{
"kind": "api#channel",
"id": "...",
"resourceId": "...",
"resourceUri": "https://content.googleapis.com/calendar/v3/calendars/primary/events?alt=json&maxResults=250&alt=json",
"expiration": "1554203159000"
}
I believe I should also be receiving a sync message, but I am not (https://developers.google.com/calendar/v3/push#sync)
To test things I am modifying an event within the calendar itself (changing the title, date, deleting, etc), and I expect something to happen in my browser, but nothing.
I'm not familiar with Push notifications in general, so not exactly sure what to expect.
I'm already authenticated and displaying events as per https://developers.google.com/calendar/quickstart/js
What am I missing? Any help is really appreciated!
Thanks in advance!
I suspect you are miss understanding exactly what push notifications is.
There are two primary ways to track when a resource has changed. You can poll that resource often and check for any changes in the resource.
For example Your application could run every five minutes and make a request to Google asking to have the event returned to you. When that event is returned you will then check if there are any changes in the event created by the user. This method of checking for changes is very time consuming and requires resources to constantly poll the server looking for changes. A better way of doing it is using Push notifications
Push notifications notify your application when a change has been made.
This document describes how to use push notifications that inform your application when a resource changes.
Its set up by enabling a watch
POST https://www.googleapis.com/calendar/v3/calendars/my_calendar#gmail.com/events/watch
Authorization: Bearer auth_token_for_current_user
Content-Type: application/json
{
"id": "01234567-89ab-cdef-0123456789ab", // Your channel ID.
"type": "web_hook",
"address": "https:/examle.com/notifications", // Your receiving URL.
...
"token": "target=myApp-myCalendarChannelDest", // (Optional) Your channel token.
"expiration": 1426325213000 // (Optional) Your requested channel expiration time.
}
This tells Googles servers that you would like to be notified when ever someone makes a change to the event. This sets up a web hook to https:/examle.com/notifications which will be notified as soon as there is a change to the event.
Name of the event, date time are normally changes i dont think you will get a push notification if someone else is added to the event.
What this is not
The server is NOT going to send you a message 10 minutes before the event is due. Thats user notification and something completely different and not part of the Google Calendar api.

Microsoft Graph API token validation failure

I would use Microsoft Graph API in my Angular Web application.
First I make connexion using msal library
When I try log in with my profil I get this error
I have configured my app as the mentionned in the official git sample
MsalModule.forRoot({
clientID: "Tenant ID",
authority: "https://login.microsoftonline.com/common/",
redirectUri: "http://localhost:4200/",
validateAuthority : true,
popUp: true
}),
Authetification is working and I get the token.
Then when I'm in home page I make a second request to Microsoft Graph API to get user information using that token.
getProfile() {
let header= new Headers();
let tokenid= sessionStorage.getItem('msal.idtoken');
header.set('Authorization', 'Bearer ' + tokenid)
let url ="https://graph.microsoft.com/v1.0/me/"
return this.http.get(url,{headers:header});
}
}
I get an 401 Unauthorized error with a response :
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "xxxxxx",
"date": "2018-10-09T22:58:41"
}
}
}
I don't know why MG API is not accepting my token, Am I using wrong authority url ?
UPDATE: I have understood that actually I get id_token which is different from access token. How can I get Access token from MSAL library to make MS GRAPH API calls ?:
According to the same sample you can also attach an HttpInterceptor that will automatically attach the access token to each (external) HTTP call.
By reading through the documentation I found the following information.
consentScopes: Allows the client to express the desired scopes that should be consented. Scopes can be from multiple resources/endpoints. Passing scope here will only consent it and no access token will be acquired till the time client actually calls the API. This is optional if you are using MSAL for only login (Authentication).
That suggests that using the HttpInterceptor doesn't only attach the access token, but also retrieves it. The token that you're seeing is probably just a token for your application, but isn't a valid token for the Graph API.
Internally it uses getCachedTokenInternal(scopes: Array<string>, user: User) to get a new access token for specific scopes code found here. I'm not sure if you can use this method as well to get a new token for that resource. I would just use the interceptor.
You could try to copy the access token and see how it looks like on jwt.ms (a Microsoft provided JWT token viewer) or jwt.io.
Any tokens valid for Graph should have the Audience of https://graph.microsoft.com, so if you inspect the token (in jwt.ms) it should at least have this value.
"aud": "https://graph.microsoft.com",
The issue is that you're using the id_token instead of the access token:
let tokenid= sessionStorage.getItem('msal.idtoken');
becomes something like:
let tokenid= sessionStorage.getItem('msal.token'); // or msal.accesstoken
Update(per Phillipe's comment)
You need to select the scopes that you want to target in your application. So, it looks like you want the user profile, so you'll want to add the consentScopes property to specify which scopes your app will use:
MsalModule.forRoot({
clientID: "Tenant ID",
authority: "https://login.microsoftonline.com/common/",
redirectUri: "http://localhost:4200/",
validateAuthority : true,
popUp: true,
consentScopes: ["user.read"]
}),
Make sure you add your endpoint to Resource Map configuration. See this link: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/MSALAngularDemoApp
export const protectedResourceMap:[string, string[]][]=[ ['https://graph.microsoft.com/v1.0/me', ['user.read']] ];

steps to integrate PowerBI report on Java application

JavaScripts Required
jquery.js
powerbi.js
Generate AAD token
I assume that you have Native application built already and all required Power BI Access has been given.If not then refer steps A to C below.
I used the steps mentioned on this link.I modified it a bit to reuse token till it get expired.Only after expiration,we will generate new token
http://community.powerbi.com/t5/Developer/Rest-api-usage-with-Java/m-p/58514#M1841
This application will give AAD token for REST call received
Create DIV for Report On JSP page of application that need Report to be embeded
div id="reportContainer" class="reportContainer"
Get AAD token
Make REST call to application developed at Step 1 get AAD token
my aadToken object has 2 parameters accessToken and expiresAtStr
var aadToken={accessToken:' ',expiresAtStr: ''};
function getAadAccessToken() {
var deferred = $q.defer();
$http.get('/MyPowerBIApp/REST/getAadToken/')
.then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
console.error('Error while getting Aad Access Token');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
After receiving aadToken from REST call, create Embed Configuration
txtAccessToken is aad token from above.(aadToken.accessToken)
txtEmbedUrl is the report that needs to be embedded.
It will be like https://app.powerbi.com/reportEmbed?reportId.......
var config= {
type: 'report',
tokenType: 0,//1:Embed,0:Aad
accessToken: txtAccessToken,
embedUrl: txtEmbedUrl,
permissions: 7,
viewMode: 0,
settings: {
filterPaneEnabled: false,
navContentPaneEnabled: false,
useCustomSaveAsDialog: false
}
};
var $reportContainer = $('#reportContainer');
var report = powerbi.embed($reportContainer.get(0), config);
This will embed report on to the DIV
Major mistakes occurred while trying to embed was on generating AAD Token.
Make sure you have created Azure application and has given all required permissions to use Power BI APIs
A. Create Native App as mentioned here:
https://learn.microsoft.com/en-us/power-bi/developer/walkthrough-push-data-register-app-with-azure-ad
B. Go to Azure Active Directory-> App registrations
Click on Your Application :
Application ID value that you see here is the clientId value that you will use for AAD Token generation mentioned on step 1
Please refer below code:
AuthenticationResult authResult = authenticationContext.acquireToken(
resourceId,
clientId,
username,
password,
null
).get();
C. Go To Azure Active Directory-> App registrations ->Settings ->Required permissions
Make sure that Power BI Service (Power BI) is under the API and all required permissions are given.Below are few of the permissions
View users Groups
View All Reports
View All Dashboards(Preview)
If all these steps are done,you should be able to embed the report with the token received.
Please check and let me know if I had missed any steps or there will be any issues on this approach.
Also Make sure that the username that will be used to generate AAD Token is having access(MemberOf) PowerBI workspace where the Report resides

Categories

Resources