Google API OAuth2, учетная запись службы, "ошибка": "invalid_grant"

Я пытаюсь использовать учетную запись службы для синхронизации календарей из программного обеспечения Dynamics CRM в Google. Во время этого я столкнулся с отсутствием документации по API Google для .net, особенно в отношении авторизации. Большинство образцов Google нельзя даже скомпилировать из-за устаревших библиотек и классов.

Итак, я нашел пример над интернированным и получил ошибку. Может ли кто-нибудь посмотреть мой образец и сказать, что я делаю неправильно?

Подготовительные шаги:

  • Я создал проект в своей личной учетной записи Google.
  • В консоли разработчика проекта в разделе APIS и AUTH → Credentials я создал учетную запись службы. Затем нажмите "Создать ключ P12" и загрузите файл .p12.
  • В APIS и AUTH → API, включен "API календаря"

Затем создалось консольное приложение и удалось установить пакеты OAuth и Calendar nuget. Есть:

  • Клиентская библиотека API Google API, Google.Apis.Auth 1.8.1
  • Клиентская библиотека API Google API, Google.Apis 1.8.1
  • Основная клиентская библиотека API Google API, Id: Google.Apis.Core 1.8.1
  • Google.APIs.Calendar.v3 Клиентская библиотека, Google.Apis.Calendar.V3 1.8.1.860

Код найден и адаптирован к моим потребностям:

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 = "[email protected]account.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();
        }
    }
}

Общение с Google API, которое я вижу в приложении и Fiddler, это:

Запрос:

Host: HTTPS accounts.google.com, URL:/o/oauth2/token
Утверждение: длинная двоичная строка
grant_type: urn: ietf: params: oauth: grant-type: jwt-bearer

Ответ:

HTTP/1.1 400 Плохой запрос Content-Type: application/json Cache-Control: no-cache, no-store, max-age = 0, must-revalidate Pragma: no-cache Истекает: Пт, 01 янв 1990 00:00:00 GMT Дата: Чт, 24 июл 2014 06:12:18 GMT X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode = block Сервер: альтернативный протокол GSE: 443: quic Transfer-Encoding: chunked

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

Fiddler screenshot

Пожалуйста, помогите и спасибо заранее!

Ответ 1

После некоторых исследований я обнаружил, что Google API не работает должным образом с вашей личной учетной записью @gmail.com. У вас должна быть учетная запись домена организации в Google в формате you @your_organisation_domain

Тогда, что также довольно запутанно, есть документация на странице Google Диска, с "Делегировать полномочия домена для вашей учетной записи службы" , не упомянутых на странице API календаря. В разделе есть 7 шагов, которые необходимо выполнить.

BTW с личным сайтом администрирования admin.google.com даже недоступен. Таким образом, невозможно выполнить эти 7 шагов с помощью учетной записи @gmail.com.

Затем, когда клиент авторизуется в консоли администратора Google Apps > Безопасность > Дополнительные настройки > Управление доступом клиента OAuth, код начинает работать.

Есть код, который работает для меня:

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 = "[email protected]account.com";
            var userAccountEmail = "[email protected]"; 
            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();
        }
    }
}

Полученный результат выглядит так:

Calendar via OAuth2 Service Account Sample
Created event Id: jkits4dnpq6oflf99mfqf1kdo0
calendar [email protected]
Event ID: 1logvocs77jierahutgv962sus, ICalUID: [email protected]
  Name: test event
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: [email protected]
  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: [email protected]
  Name: test event updated
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: [email protected]
  Recurrence: no recurrence
  Start: 2014-08-14
  End: 2014-08-15
  Reminders: Not defailt, email:10
=====================
Event ID: 974hqdhh8jhv5sdobkggmdvvd8, ICalUID: [email protected]
  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: [email protected]
  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: [email protected]
  Name: Calendar event via API
  Description: Programmatically created
  Status: confirmed
  Color: 6
  Attendees: 
  Kind: calendar#event
  Location: 
  Organizer: [email protected]
  Recurrence: no recurrence
  Start: 2/08/2014 12:30:50 PM
  End: 2/08/2014 1:30:50 PM
  Reminders: Not defailt, popup:60
=====================

Первое событие - это повторяющиеся еженедельные серии событий на целый день. Во втором обновляется одно событие первого (имеет тот же UID). Третье - одно событие в течение одного часа. Последнее создано кодом выше.

Надеюсь, это поможет другим экономить свое время.

Ответ 2

Я не совсем уверен, что не так с вашим кодом. Я думаю, его часть, как вы загружаете файл ключа. Это также может быть тот факт, что вам не нужно отправлять Пользователь с помощью ServiceAccountCredential, поскольку учетная запись службы является пользователем, к которому вы входите. Я не уверен, почему вы отправляете кому-то электронную почту Gmail.

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 = "[email protected]eaccount.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;

        }
    }
}

Этот код проверен, он показывает вам, как создать начальный календарь для учетной записи службы. Без создания календаря он вернет 0 календарей, вам нужно будет сначала создать их.

Ответ 3

У меня была такая же проблема, что она отлично работала на удаленном сервере, но на локальном сервере я добрался до этой ошибки, длинная история за часами головной боли, которую я обнаружил, была связана с моими системными часами, просто пройдите этот шаг

1. Нажмите на часы в своей системе

  • Это вызовет календарь и время

2. Нажмите Изменить настройки даты и времени...

  • Появится диалоговое окно "Дата и время"

3. Нажмите вкладку "Время в Интернете"

4. Нажмите "Изменить настройки"

  • Появится диалоговое окно "Настройки времени Интернета".

затем, наконец, обновите часы с помощью одного из серверов времени