13

I'm trying to use service account to sync calendars from Dynamics CRM software to Google. During this I faced lack of documentation on google API for .net, especially regarding authorization. Most of Google samples can't be even compiled because of outdated libraries and classes used.

So I found some example over interned and receive error. Could someone please look on my sample and tell what am I doing wrong?

Preparatory steps:

  1. I created a project in my private Google account.
  2. In project developer console, under APIS & AUTH -> Credentials, I generated Service Account. Then clicked on "Generate P12 key" and downloaded the .p12 file.
  3. Under APIS & AUTH -> APIs, switched on "Calendar API"

Then created console app and managed to install OAuth and Calendar nuget packages. There are:

  1. Google APIs Auth Client Library, Google.Apis.Auth 1.8.1
  2. Google APIs Client Library, Google.Apis 1.8.1
  3. Google APIs Core Client Library, Id: Google.Apis.Core 1.8.1
  4. Google.APIs.Calendar.v3 Client Library, Google.Apis.Calendar.V3 1.8.1.860

There is a code found and adapted to my needs:

using System;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Calendar.v3;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace CrmToGoogleCalendar
{
    class Program
    {

        static void Connect()
        {
            var certificate = new X509Certificate2("My Project-ee7facaa2bb1.p12", "notasecret", X509KeyStorageFlags.Exportable);


            var serviceAccountEmail = "506310960175-q2k8hjl141bml57ikufinsh6n8qiu93b@developer.gserviceaccount.com";
            var userAccountEmail = "<my email>@gmail.com";
            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail) 
                {
                    User = userAccountEmail,
                    Scopes = new[] { CalendarService.Scope.Calendar }
                }
                .FromCertificate(certificate));

            var service = new CalendarService(new BaseClientService.Initializer()
            {
                ApplicationName = "Test calendar sync app",
                HttpClientInitializer = credential

            });

            var calList = service.CalendarList.List().Execute().Items;


            foreach (var cal in calList)
            {
                Console.WriteLine(cal.Id);
            }
        }


        static void Main(string[] args)
        {
            Connect();
        }
    }
}

The communication with Google API which I see in the app and Fiddler is:

Request :

Host: HTTPS accounts.google.com, URL: /o/oauth2/token
Assertion: long binary string
grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer

Response:

HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Date: Thu, 24 Jul 2014 06:12:18 GMT X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Server: GSE Alternate-Protocol: 443:quic Transfer-Encoding: chunked

1f { "error" : "invalid_grant" } 0

Fiddler screenshot

Please help and thanks in advance!

Dimitry
  • 401
  • 1
  • 4
  • 9
  • Have you tried the answers from http://stackoverflow.com/questions/10576386/invalid-grant-trying-to-get-oauth-token-from-google ? – luc Jul 26 '14 at 09:27
  • Hi @luc. I found, that after opening calender in google apps web page, it started to response with `Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""`. So the same code another error. I still think I missed something in configuring google properly – Dimitry Jul 29 '14 at 05:51
  • 1
    I was getting this error on the production server. After searching through answers like this, I finally figured out it was due to the system clock being off. http://stackoverflow.com/questions/11973162/google-analytics-api-oauth-exception-invalid-grant-with-service-account-same?rq=1 – KevinVictor Mar 26 '15 at 17:25

3 Answers3

8

After some investigations I found, that Google API does not work as expected with your personal account @gmail.com. You should have organization domain account in Google in format you@your_organisation_domain

Then, what is also pretty confusing, there is documentation at Google Drive API page, with "Delegate domain-wide authority to your service account" section not mentioned at Calendar API page. There are 7 steps in the section, are required to be done.

BTW with personal account administration site admin.google.com is even unavailable. So it's impossible to perform these 7 steps with @gmail.com account.

Then, when client is authorised in Google Apps Admin Console > Security > Advanced settings > Manage OAuth Client access the code starts to work.

There is a code that works for me:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace CrmToGoogleCalendar
{
    class Program
    {

        static void Connect()
        {
            Console.WriteLine("Calendar via OAuth2 Service Account Sample");

            var certificate = new X509Certificate2("My MC Project-3f38defdf4e4.p12", "notasecret", X509KeyStorageFlags.Exportable);
            var serviceAccountEmail = "795039984093-c6ab1mknpediih2eo9cb70mc9jpu9h03@developer.gserviceaccount.com";
            var userAccountEmail = "me@testdomain.com"; 
            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail) 
                {
                    User = userAccountEmail,
                    Scopes = new[] { CalendarService.Scope.Calendar }
                }
                .FromCertificate(certificate));

            var service = new CalendarService(new BaseClientService.Initializer()
            {
                ApplicationName = "Test calendar sync app",
                HttpClientInitializer = credential

            });

            /* Get list of calendars */
            var calList = service.CalendarList.List().Execute().Items;
            var myCalendar = calList.First(@c => @c.Id == userAccountEmail);

            /* CREATE EVENT */
            var event1 = new Event()
                {
                    Kind = "calendar#event",
                    Summary = "Calendar event via API",
                    Description = "Programmatically created",
                    Status = "confirmed",
                    Organizer = new Event.OrganizerData() {
                        Email = userAccountEmail
                    },
                    Start = new EventDateTime()
                        {
                            DateTime = DateTime.Now.AddDays(1)
                        },
                    End = new EventDateTime()
                    {
                        DateTime = DateTime.Now.AddDays(1).AddHours(1)
                    },
                    ColorId = "6",
                    Reminders = new Event.RemindersData()
                        {
                            UseDefault = false,
                            Overrides = new List<EventReminder>(
                                new [] {
                                    new EventReminder()
                                        {
                                            Method = "popup",
                                            Minutes = 60
                                        }
                                })
                        }
                };

            event1 = service.Events.Insert(event1, myCalendar.Id).Execute();
            Console.WriteLine("Created event Id: {0}", event1.Id);


            /* ENLIST EVENTS */
            Console.WriteLine("calendar id={0}", myCalendar.Id);
            var events = service.Events.List(myCalendar.Id).Execute();
            foreach (var @event in events.Items)
            {
                Console.WriteLine("Event ID: {0}, ICalUID: {1}", @event.Id, @event.ICalUID);
                Console.WriteLine("  Name: {0}", @event.Summary);
                Console.WriteLine("  Description: {0}", @event.Description);
                Console.WriteLine("  Status: {0}", @event.Status);
                Console.WriteLine("  Color: {0}", @event.ColorId);
                Console.WriteLine("  Attendees: {0}", @event.Attendees == null ? "" : @event.Attendees.Select(a => a.Email).ToString());
                Console.WriteLine("  Kind: {0}", @event.Kind);
                Console.WriteLine("  Location: {0}", @event.Location);
                Console.WriteLine("  Organizer: {0}", @event.Organizer.Email);
                Console.WriteLine("  Recurrence: {0}", @event.Recurrence == null ? "no recurrence" : String.Join(",", @event.Recurrence));
                Console.WriteLine("  Start: {0}", @event.Start.DateTime == null ? @event.Start.Date : @event.Start.DateTime.ToString());
                Console.WriteLine("  End: {0}", @event.End.DateTime == null ? @event.End.Date : @event.End.DateTime.ToString());
                Console.WriteLine("  Reminders: {0}", @event.Reminders.UseDefault.Value ? "Default" : "Not defailt, " + 
                    (@event.Reminders.Overrides == null ? "no overrides" : String.Join(",", @event.Reminders.Overrides.Select(reminder => reminder.Method + ":" + reminder.Minutes)))
                    );
                Console.WriteLine("=====================");
            }

            Console.ReadKey();
        }


        static void Main(string[] args)
        {
            Connect();
        }
    }
}

The output produced looks so:

Calendar via OAuth2 Service Account Sample
Created event Id: jkits4dnpq6oflf99mfqf1kdo0
calendar id=me@testdomain.com
Event ID: 1logvocs77jierahutgv962sus, ICalUID: 1logvocs77jierahutgv962sus@google.com
  Name: test event
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: me@testdomain.com
  Recurrence: RRULE:FREQ=WEEKLY;BYDAY=TH
  Start: 2014-07-31
  End: 2014-08-01
  Reminders: Not defailt, email:10,popup:10
=====================
Event ID: 1logvocs77jierahutgv962sus_20140814, ICalUID: 1logvocs77jierahutgv962sus@google.com
  Name: test event updated
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 2014-08-14
  End: 2014-08-15
  Reminders: Not defailt, email:10
=====================
Event ID: 974hqdhh8jhv5sdobkggmdvvd8, ICalUID: 974hqdhh8jhv5sdobkggmdvvd8@google.com
  Name: One hour event
  Description: test description
  Status: confirmed
  Color: 7
  Attendees: 
  Kind: calendar#event
  Location: Meeting Room Hire, Broadway, 255 The Bdwy, Broadway, NSW 2007, Australia
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 1/08/2014 10:00:00 AM
  End: 1/08/2014 11:00:00 AM
  Reminders: Default
=====================
Event ID: jkits4dnpq6oflf99mfqf1kdo0, ICalUID: jkits4dnpq6oflf99mfqf1kdo0@google.com
  Name: Calendar event via API
  Description: Programmatically created
  Status: confirmed
  Color: 6
  Attendees: 
  Kind: calendar#event
  Location: 
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 2/08/2014 12:30:50 PM
  End: 2/08/2014 1:30:50 PM
  Reminders: Not defailt, popup:60
=====================

First event is recurrent weekly whole day event series. Second is updated single event of the first (has the same UID). Third is single event for one hour. The last is created by code above.

Hope this will help others to economy their time.

Dimitry
  • 401
  • 1
  • 4
  • 9
  • Are you sure about this? I need to have a mailbox with google to be able to access these services? – Hemant.Gupta Oct 08 '14 at 13:27
  • I'm having the same issue. And now what I understand from your answer is that if I want to use a **Service account** to access Google APIs (in my case the **Calendar APIs**) it is mandatory (but not explained by Google!) to have a **Google Admin Console account**, that is a free 30 day trial service, that then become a paid service... Right? So I can only use Web Application account... – Cheshire Cat Mar 02 '15 at 10:38
  • @CheshireCat You can use your personal gmail account to access Google Calendar API. Service account is used for Domain-wide authentication. – skyfree Apr 17 '15 at 03:46
  • 1
    [This](http://stackoverflow.com/questions/28981321/google-calendar-api-v3-net-authentication-with-service-account-or-web-applicati) was my question. I finally make it work using Service Account. See the section **UPDATE** at the end of my question. – Cheshire Cat Apr 17 '15 at 10:48
  • @CheshireCat, does the service account work with any regular gmail account? I mean if we do not have any domain account? Without domain account it is not possible to login to the domain admin control panel. But developer console is available for any gmail account. – Rahatur Feb 18 '16 at 18:48
2

i was having same problem it was working fine on remote server but on the local server i got to that error, long story short after hours of headache i found out problem was from my system clock,just go through these step

1. Click on the clock in your system

  • This will bring up the calendar and time

2. Click Change date and time settings...

  • The Date and Time dialog will appear

3. Click on the Internet Time tab

4. Click Change settings

  • The Internet Time Settings dialog will appear.

then finally update your clock with one of the time servers

Abolfazl
  • 1,592
  • 11
  • 32
0

I'm not really sure what is wrong with your code I think its part how you are loading the key file. It could also be the fact that you don't need to send User with ServiceAccountCredential since the service account is the User you are loging in to. I am not sure why you are sending someones Gmail email.

using Google.Apis.Auth.OAuth2;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Calendar.v3;
namespace GoogleAnalytics.Service.Account
{
    class Program
    {
        static void Main(string[] args)
        {
            //Install-Package Google.Apis.Calendar.v3
            string serviceAccountEmail = "1046123799103-6v9cj8jbub068jgmss54m9gkuk4q2qu8@developer.gserviceaccount.com";
            var certificate = new X509Certificate2(@"C:\Users\HP_User\Documents\GitHub\Google-Analytics-dotnet-ServiceAccount\GoogleAnalytics.Service.Account\Diamto Test Everything Project-bc63fd995bd7.p12", "notasecret", X509KeyStorageFlags.Exportable);

            ServiceAccountCredential credential = new ServiceAccountCredential(
                   new ServiceAccountCredential.Initializer(serviceAccountEmail)
                   {
                       Scopes = new[] { CalendarService.Scope.Calendar }
                   }.FromCertificate(certificate));

            // Create the service.
            var service = new CalendarService(new CalendarService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "CalendarService API Sample",
            });

            // define the new Calendar
            Google.Apis.Calendar.v3.Data.Calendar calendar = new Google.Apis.Calendar.v3.Data.Calendar();
            calendar.Description = "New Calendar";
            calendar.Summary = "New Calendar Summary";
            // Insert the Calendar
            service.Calendars.Insert(calendar).Execute();
            // List The Calendar
            var calList = service.CalendarList.List().Execute().Items;

        }
    }
}

This code is tested it shows you how to create the initial calender for the service account. Without creating a calendar it would return 0 calendars, you need to remember to create one first.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks for your comment. I'm using my gmail email because i'm using my private account. It's not domain account, it's my google gmail one. – Dimitry Jul 29 '14 at 05:21
  • When I omit User in ServiceAccountCredential it answers with 0 calendars. I opened my google calendar and saw a default calendar and tasks. Still code return either error when user specified or no data if unspecified. After I opened the calendar error text changed to `Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""` – Dimitry Jul 29 '14 at 05:36
  • A service account is not you it its it's own entity defined by the service account email address. Even though you personally created the service account in dev console this does not mean that it has access to your Google calendar, because its not you. you can try and add the service account email address to your calendar this might give it access to your calendar but I haven't tried it. – Linda Lawton - DaImTo Jul 29 '14 at 06:32
  • Thanks @DalmTo, I found an answer and you're right - wrong account was used. The API is not intended to be working with personal account, despite it's not mentioned on Calendar API page. There is a good doc on Drive page though. – Dimitry Jul 30 '14 at 02:21
  • if you found a discrepancy in the documentation scroll down to the bottom of the page there should be a feedback link. Send in some feed back Google goes through the documentation every 3 months if I remember correctly. This will ensure that its fixed for the next person. – Linda Lawton - DaImTo Jul 30 '14 at 07:07
  • a service accounts uses an "impersonate email" -- otherwise, how does it change anything in Drive? It impersonates a user, without their consent, as long as their in the domain – Don Cheadle May 05 '15 at 16:37