I'm using Google Calendar API with a service account. Actually I'm going to create an application that synchronize the events from a local database to a specific email user. The adding of an event seems working pretty well but the problem's coming when I need to update an event. In particular before the update I check if the event exist or not on the Google Calendar of a specific user, let me show an example, this is how I create the service account:
string[] scopes = new string[]
{
CalendarService.Scope.Calendar,
CalendarService.Scope.CalendarReadonly
};
var certificate = new X509Certificate2(CPath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("my service account email")
{
Scopes = scopes
}.FromCertificate(certificate));
CalendarService service = new CalendarService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName
});
return service;
before updating or adding an event I check if the event exist in this way:
public bool EventExist(string id)
{
try
{
var service = AuthenticationService();
Event foundResponse = service.Events.Get("foo#gmail.com", id).Execute(); //id is the id of the event
return true;
}
catch (Exception ex)
{
Log.Write(ex.Message);
return false;
}
}
Now AuthenticationService() service is the method that return the service variable (the first code wrote). Later, I use the service variable for get the event of a specific user, note that the email is the primary calendar of the user. Now if I pass "primary", the event was found, instead if I pass the email, as in this case, the event was not found.
I don't know why, maybe the service-account save the event of a specific user somewhere? This situation is very bad for me, 'cause if an user delete an event with a particular id I need to check if the element exist or not into the calendar of the user.
Maybe I'm wrong with something or Google don't allow to do this?
Related
In .NET 7, without specifying a User in the
ServiceAccountCredential.Initializer(original.Id)
block, my calendar event can be created with no errors. But if I specify a user as shown below, I get this error:
Error: unauthorized_client,
Description: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
My service account has domain-wide delegation, allowed scope of
calendar via OAuth2, and I have specifically granted the SA access to the user's calendar (when I try on accounts that have not specifically granted access, I can't create a calendar entry at all).
How do I get appropriate credentials for the service account to impersonate the end user within my domain?
Credit for getting this far to creating-a-serviceaccountcredential-for-a-user-from-a-systems-account
Previous answers don't seem to be working on latest release.
In my API code, which calls the authentication service then creates the event:
Google.Apis.Calendar.v3.CalendarService CS = await SA.AuthenticateServiceAccountSO();
var result = CS.Events.Insert((Event)newEvent, clientemail).Execute();
In Service AuthenticateServiceAccountSO():
public async Task<CalendarService> AuthenticateServiceAccountSO()
{
await Task.Delay(1);
string? serviceAccountCredentialFilePath = _config.GetValue<string>("ServiceAccountFilePath");
string? ClientEmail = _config.GetValue<string>("TestingUserAccountEmail");
string[] scopes = new string[] { CalendarService.Scope.Calendar };
ServiceAccountCredential original = (ServiceAccountCredential)GoogleCredential.FromFile(serviceAccountCredentialFilePath).UnderlyingCredential;
var initializer = new ServiceAccountCredential.Initializer(original.Id)
{
User = ClientEmail,
Key = original.Key,
Scopes = scopes
};
var credential = new ServiceAccountCredential(initializer);
CalendarService service = new(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar_Appointment event Using Service Account Authentication"
});
return service;
}
You're currently copying relatively little of the service account credential - in particular, there's no KeyId. But you can do it all much more simply anyway:
var credential = GoogleCredential.FromFile(serviceAccountCredentialFilePath)
.CreateScoped(CalendarService.Scope.Calendar)
.CreateWithUser(ClientEmail);
I am supporting an IT admin, who is himself facilitating the use of compliance software. To that end, I have written some C# code that iterates through all users in a directory, and performs operations on their messages. My current solution uses two different APIs to accomplish this (code snippet below), but obviously it would be better to only use one API. Having scanned through other posts here, I failed to find a satisfactorily clear answer on how to make that happen. My app is a service account, with Google Workspace domain-wide delegation enabled. How can I use only one API to accomplish what I am doing with two?
[working code snippet]
string domain; // domain name
string adminEmail; // admin e-mail
string directoryClientEmail; // client e-mail for Directory API
string directoryPrivateKey; // private key for Directory API
string directoryPrivateKeyId; // private key ID for Directory API
string gmailClientEmail; // client e-mail for Gmail API
string gmailPrivateKey; // private key for Gmail API
string gmailPrivateKeyId; // private key ID for Gmail API
CancellationToken cancellationToken; // a cancellation token
DirectoryService directoryClient = new DirectoryService(
new BaseClientService.Initializer
{
HttpClientInitializer = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(creds.DirectoryClientEmail)
{
User = adminEmail,
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
Key = RSA.Create(directoryPrivateKey),
KeyId = directoryPrivateKeyId
}.FromPrivateKey(directoryPrivateKey))
});
UsersResource.ListRequest userListRequest = directoryClient.Users.List();
userListRequest.Domain = domain;
Users userList = await userListRequest.ExecuteAsync(cancellationToken);
foreach (User user in userList)
{
GmailService gmailClient = new GmailService(
new BaseClientService.Initializer
{
HttpClientInitializer = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(gmailClientEmail)
{
User = user.PrimaryEmail,
Scopes = new[] { GmailService.Scope.MailGoogleCom },
Key = RSA.Create(gmailPrivateKey),
KeyId = gmailPrivateKeyId
}.FromPrivateKey(gmailPrivateKey))
});
ListRequest listRequest = new ListRequest(gmailClient, "me");
ListMessageResponse listMessageResponse = await listRequest.ExecuteAsync(cancellationToken);
foreach (Message message in listMessageResponse.Messages)
{
// do stuff
}
}
To achieve what you want, you can't only use one API. As the Gmail API will not give you the users in the domain, but you can get a user's messages with it; which you need. So Gmail API is a requirement.
Then if you want an up-to-date domain users list, then you need to use the Directory API, so unless you have a list of users somewhere else, you require this API too.
I want to send notification when deleting an event :
var certificate = new X509Certificate2("myp12filepath", "notasecret", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = Scopes
}.FromCertificate(certificate));
BaseClientService.Initializer initializer = new BaseClientService.Initializer();
initializer.HttpClientInitializer = credential;
initializer.ApplicationName = ApplicationName;
var service1 = new CalendarService(initializer);
var googleCalendarEvent = service1.Events.Delete("calendarId", "eventId").Execute();
The delete function does not accept a third parameter (indicating whether or not to send a notification) as mentioned in this link (there is no c# example)
So is there a way to send email notifications after deleting an event?
Create an instance of delete request and assign notification as true. See below.
var certificate = new X509Certificate2("myp12filepath", "notasecret", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = Scopes
}.FromCertificate(certificate));
BaseClientService.Initializer initializer = new BaseClientService.Initializer();
initializer.HttpClientInitializer = credential;
initializer.ApplicationName = ApplicationName;
var service1 = new CalendarService(initializer);
EventsResource.DeleteRequest delReq = service1.Events.Delete("calendarId", "eventId");
delReq.SendNotifications = true;
var googleCalendarEvent = delReq.Execute();
AFAIK, you need to set sendNotifications to true if you want to send notifications about the deletion of the event as also mentioned in your given link. And in addition to that, for a user to receive notifications, user's notification setting should also be checked for each of the calendars as mentioned in this forum and also as given here.
Listed below are the steps to turn notification on or off:
You can choose whether to have notifications for events, and whether you want to get notifications over email or in your browser.
Open Google Calendar on your computer.
In the top right, click Settings (gear icon) > Settings.
At the top of the page, click the Calendars tab.
Next to your calendar's name, click Edit notifications.
Click Add notification or edit an existing notification.
At the bottom of the page, click Save.
Note: To get notifications on your computer, you need to have Google Calendar open in your browser.
Lastly, since there is not much sample for C#, the .NET client might help.
I am able to retrieve event from google calendar API using OAuth authentication by code given here, but how to code for getting event information using API key.
API key is for use with PUBLIC data. For an API key to work with a calendar I would assume said calendar would have to be set to public.
var service = new CalendarService(new BaseClientService.Initializer()
{
ApiKey = "XXX",
ApplicationName = "xyz",
});
var events = service.Events.List("en.danish#holiday#group.v.calendar.google.com").Execute();
foreach (var myEvent in events.Items)
{
Console.WriteLine(string.Format("Event: {0} Start: {1} End: {2}", myEvent.Summary, myEvent.Start.DateTime.ToString(), myEvent.End.DateTime.ToString()));
}
I have an example / tutorial of accessing events on public calendars here
I think you should consider looking at a service account instead this will allow you to grant the service account access to your calendar and you wont need to go though the Oauth2 authentication which I suspect is what you don't want to be doing.
string[] scopes = new string[] {
CalendarService.Scope.Calendar, // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail) {
Scopes = scopes
}.FromCertificate(certificate));
I have a tutorial on how to us a service account here
I have been using the old version of Google Calendar GData API (v1, v2) since November 2011 in my ASP.NET Applications, allowing Users to retrieve and/or create Calendar Events after submitting their usernames and passwords , and this was working perfectly till 17th of November 2014 just before Google decided to shut down this version of API as announced Calendar GData API / Google Calendar Connectors deprecation
Now I am stuck with the new version of Google APIS Calendar (v3) which forces me to use different scenario of Authentication Process instead of the Traditional one. I don't mind at all using this version of Calendar API as it supports all the needed features but now i don't know how to handle multiple users authentication to use their User Client's ID and Secret which are registered per each user Code Console.
So my question is : Is there any way to let user sign in with his/her normal credentials (Either by Username/Password or Google+ Sign UP feature) and bypassing the process of creating API Project, Enabling the needed APIs and creating new User credentials inside the Console through ASP.net code?
Any Sample code made in C# ASP.net is highly appreciated .
EDIT: Here is my code of Authentication I use
public static CalendarService Authenticate()
{
CalendarService service;
GoogleAuthorizationCodeFlow flow;
string json_File = System.Configuration.ConfigurationManager.AppSettings["Authentication_Path"];
string store_path = System.Configuration.ConfigurationManager.AppSettings["FileStore_Path"];
string url = System.Configuration.ConfigurationManager.AppSettings["Authent_URL"];
using (var stream = new FileStream(json_File, FileMode.Open, FileAccess.Read))
{
flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
DataStore = new FileDataStore(store_path),
ClientSecretsStream = stream,
Scopes = new[] { CalendarService.Scope.Calendar }
});
}
var uri = url;
var result = new AuthorizationCodeWebApp(flow, uri, uri).AuthorizeAsync("TRAININGCALENDAR", CancellationToken.None).Result;
if (result.Credential == null)
{
GoogleCalendar_Bus.Main_Authentication(url, "", "");
}
// The data store contains the user credential, so the user has been already authenticated.
service = new CalendarService(new BaseClientService.Initializer
{
ApplicationName = "Calendar API Sample",
HttpClientInitializer = result.Credential
});
if (result.Credential != null)
{
service = new CalendarService(new BaseClientService.Initializer
{
ApplicationName = "Calendar API Sample",
HttpClientInitializer = result.Credential
});
}
return service;
}
No you need to use Oauth2. When they authenticate you just save the refresh token this will allow you to then get a new access token and you will have access again. You will need to make your own implementation of Idatastore to store these refresh tokens in the database.
The code for creating an implementation of a Idatastore that stores to the Database is to extensive to post here but you can see a basic example here: DatabaseDataStore.cs
Then you can use it like this.
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets { ClientId = _client_id
,ClientSecret = _client_secret }
,scopes
,Environment.UserName
,CancellationToken.None
,new DatabaseDataStore(#"LINDAPC\SQL2012", "LindaTest", "test123", "test", "test")).Result;
Update: Now that I can see your code.
Make sure you have the latested .net client lib. Google.Apis.Calendar.v3 Client Library
your code is using FileDataStore this is what you will need to change. You need to make your own implementation of Idatastore similar to the one I have created DatabaseDatastore.
Your code looks different from how I normally do it.
string[] scopes = new string[] {
CalendarService.Scope.Calendar , // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
try
{
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore("Daimto.GoogleCalendar.Auth.Store")).Result;
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
return null;
}
This may be due to the fact that you aren't using the most up to date client lib, you can find a sample console application for Google Calendar here unfortunately it also uses FileDatastore you will have to edit it to use DatabaseDataStore. The authentication sample project can be found here Google-Dotnet-Samples/Authentication/ it shows how you can create your own implementation of Idatastore.
I am still working on the tutorial to go along with that sample project I hope to have it completed soon.