2

I'm having a headache because of google calendar API. My goal is to access my private calendar, and share some details through a web interface I will create, the problem is that after following all of the steps, I still get the response as empty and no errors to know exactly what the problem is... steps I have done so far:

  1. Created a service account and downloaded a json file.
  2. Went to admin.google.com > Security > Access and data control > API Controls > Domain-wide delegation as follows

enter image description here

  1. Added my C# code as follows:

GoogleCredential credential;
            using (var stream = new FileStream("googlekey2.json", FileMode.Open, FileAccess.Read))
            {

                stream.Position = 0;

                credential = GoogleCredential.FromStream(stream)
                             .CreateScoped(new string[] { CalendarService.Scope.Calendar, CalendarService.Scope.CalendarEvents });
            }

            Service = new CalendarService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Google Test App",
            });

and the response is just an empty object, with no errors...

enter image description here

Thank you for any help you may bring on how to correctly setup this on .net core app to show some results, some calendar information, I also did share my calendar with the service account e-mail address as suggested by some users around here but the result is the same, no errors but simply nothing inside...

In regards of ms Linda's answer, yes I did setup DOMAIN WIDE DELEGATION with the roles I want but I can't make it work for some reason.

OTHER INFO: I'm not trying to make a user login with a google form or similar, this is for server - server communication, in other words it should access my calendar without any interaction from the user, no user should be involved, this is why I'm taking the "Service Account" option with domain delegation.

Thank you for any help.

Guillermo Perez
  • 589
  • 4
  • 10
  • Does this answer your question? [Google Calendar API don't return shared calendars](https://stackoverflow.com/questions/60036292/google-calendar-api-dont-return-shared-calendars) – Century Tuna Mar 03 '23 at 22:43

3 Answers3

0

Google calendar only supports service accounts when you configure domain wide delegation to a google workspace domain.

You cant use it with a standard gmail user google calendar account.

var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
            {
                User = workspaceUser,
                Scopes = new[] { CalendarService.Scope.Calendar}

            }.FromCertificate(certificate);
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thank you Linda, yes I did setup the domain wide delegation, didn't I mention this? in regards of your code sample, I would like to know if the provided sample is the correct implementation? I'm guessing that parameter "serviceAccount" is "client_email": "service-account-1@taxbookeeping-212903.iam.gserviceaccount.com"?? and workspaceUser is?? and certificate is what exactly?? this is why there's so much confusion around it... it's not clear what goes in what place... – Guillermo Perez Mar 03 '23 at 22:03
0

based on your description I believe you might be using the service account credentials directly to make this call, since this is calendar info, could you try using the admin credentials to log in instead? Admins should have access to all other domain user's calendars.

Rene Olivo
  • 526
  • 1
  • 10
  • Thank you... not sure if I understand. this will be a machine to machine communication, nobody will enter credentials in a dialog, my understanding is that this is why we have the json file, or am I misunderstanding? thank you for any help... – Guillermo Perez Mar 04 '23 at 22:41
  • Hi Guillermo! Well each Json file actually contains a credential that is used to access the desired data, if the credentials specified in the file do not have clearance to access a specific type of data then an error would show up stating something along the lines of "invalid credentials" So with that being said I was unable to find an example in C# but here is one on Java: https://stackoverflow.com/a/71530565/17987690 I believe that it is just changing the email there so maybe you can change the servece account's email with your admin account? – Rene Olivo Mar 05 '23 at 01:36
0

There are nothing wrong with the authentication, unfortunately the API support guys were willing to help but unable to do anything on .net, so far I know you can do the authentication with the following 3 examples:

EXAMPLE 1

var scopes = new string[] { CalendarService.Scope.Calendar, CalendarService.Scope.CalendarEvents };

GoogleCredential credential;
using (var stream = new FileStream("MYJSONKEY.json", FileMode.Open, FileAccess.Read))
{
    stream.Position = 0;

    credential = GoogleCredential.FromStream(stream)
        .CreateScoped(scopes)
        .CreateWithUser("USER@GOOGLEORDOMAIN.COM");
}

Service = new CalendarService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = "Google Authentication Sample",
});

EXAMPLE 2

var scopes = new string[] { CalendarService.Scope.Calendar, CalendarService.Scope.CalendarEvents };

string fileContent = File.ReadAllText("MYJSONKEY.json");

GoogleKeyObj? googleObject = JsonSerializer.Deserialize<GoogleKeyObj>(fileContent);

var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(googleObject.client_email)
{
    Scopes = scopes,
    User = "USER@GOOGLEORDOMAINUSER.COM",
}.FromPrivateKey(googleObject.private_key));

Service = new CalendarService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,                
    ApplicationName = "Google Authentication Sample",
});

//----------------- ADD class object

private class GoogleKeyObj
{
    public string type { get; set; }
    public string project_id { get; set; }
    public string private_key_id { get; set; }
    public string private_key { get; set; }
    public string client_email { get; set; }
    public string client_id { get; set; }
    public string auth_uri { get; set; }
    public string token_uri { get; set; }
    public string auth_provider_x509_cert_url { get; set; }
    public string client_x509_cert_url { get; set; }
}

EXAMPLE 3

 var scopes = new string[] { CalendarService.Scope.Calendar, CalendarService.Scope.CalendarEvents };

var certificate = new X509Certificate2("cert.p12", "notasecret", X509KeyStorageFlags.Exportable);

var gsuiteUser = "USER@GOOGLEORDOMAINUSER.COM";

string serviceAccount = "SERVICEACCT@SERVICEACCT.iam.gserviceaccount.com";


var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
{
    User = gsuiteUser,   // Service account will run as this user
    Scopes = scopes

}.FromCertificate(certificate);

var credential = new ServiceAccountCredential(serviceAccountCredentialInitializer);
if (!credential.RequestAccessTokenAsync(CancellationToken.None).Result)
    throw new InvalidOperationException("Access token failed.");

Service = new CalendarService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = "Google Authentication Sample",
});

Now, the million dollar question... for google, there's not enough to say, I want the data, they expect you to apply a method they call execute() otherwise it just send an empty object, for example to get the list of calendars in a blazor page and view this in the browser console (assuming you created an object called gs that you injected first) you should go like this:

@page "/calendar"
@using System.Text.Json;

@inject GoogleService gs
@inject IJSRuntime js

<h3>Calendar</h3>

@code {

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await js.InvokeVoidAsync("console.log", "CALENDAR LIST", gs.Service.CalendarList.List().Execute());
            await js.InvokeVoidAsync("console.log", "CALENDAR META DATA", gs.Service.Calendars.Get("REPLACEWITHMYUSER@MYDOMAIN.COM").Execute());
            await js.InvokeVoidAsync("console.log", "CALENDAR EVENTS", gs.Service.Events.List("REPLACEWITHMYUSER@MYDOMAIN.COM").Execute());

        }
    }

}
Guillermo Perez
  • 589
  • 4
  • 10