Почему Запрос Syn c в HomeGraph API возвращает 403 Запрещено? - PullRequest
0 голосов
/ 31 января 2020

Проблема

Когда я вызываю «Запрос синхронизации c» в Google HomeGraph API, я получаю ответ «403 Запрещено».

Фон

Я написание «Умного дома» и успешно реализовали SYN C, QUERY и EXECUTE. Тестируя на своем мобильном телефоне, я могу видеть и взаимодействовать с устройствами в порядке. Сейчас я пытаюсь реализовать запрос Syn c, но не могу взаимодействовать с API. Я делаю то, что кажется успешным, запросы на токен доступа. Маркер всегда начинается с "ya29. c." который в моем наивном понимании предполагает пустой заголовок и полезную нагрузку (пробуя его на https://jwt.io). Однако при тестировании на https://accounts.google.com/o/oauth2/tokeninfo?access_token= он кажется действительным, показывая как уникальный идентификатор моей учетной записи службы, так и предполагаемую область. Когда я звоню в API, либо публикую данные вручную, либо через собственный код Google, это дает мне грубую ошибку 403. Я не знаю, где я могу получить больше информации об этой ошибке, кроме объектов исключения. Я новичок в GCP и не могу найти какой-либо журнал. Учитывая, что я пробовал разные методы и все возвращают 403, я склонен подозревать, что проблема связана с учетной записью или учетными данными, а не с кодом, но не может быть уверенным.

Ключ API

(я больше не могу воспроизвести какие-либо ошибки, связанные с отсутствием или недействительностью ключей API).

Хотя в документации это не показано, я видел, что некоторые люди используют Ключ API. Когда я не включаю ключ API с сертификатом p12 или включаю неверный, это приводит к ошибкам (либо с отсутствующим ключом API, либо с неправильным ключом API). Я создал неограниченный ключ API в IAM и использую его. Я не могу явно связать это с HomeGraph API, но он говорит, что он может вызывать любой API.

Код

В этом примере извлекается токен доступа, затем выполняется попытка вызова API через POST с ключом API и без него. Затем он пытается аутентифицировать и вызывать API через код библиотеки Google. Каждый сбой с 403.

using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.HomeGraphService.v1;
using Google.Apis.HomeGraphService.v1.Data;
using Google.Apis.Services;
using Lambda.Core.Constants;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using static Google.Apis.HomeGraphService.v1.DevicesResource;

public class Example
{
    public void RequestSync()
    {
        const string UrlWithoutKey = @"https://homegraph.googleapis.com/v1/devices:requestSync";
        const string UrlWithKey = @"https://homegraph.googleapis.com/v1/devices:requestSync?key=" + OAuthConstants.GoogleApiKey;
        string accessToken = this.GetAccessToken();

        // Manual Attempt 1
        try
        {
            string response = this.CallRequestSyncApiManually(accessToken, UrlWithoutKey);
        }
        catch (WebException ex)
        {
            // Receive 403, Forbidden
            string msg = ex.Message;
        }

        // Manual Attempt 2
        try
        {
            string response = this.CallRequestSyncApiManually(accessToken, UrlWithKey);
        }
        catch (WebException ex)
        {
            // Receive 403, Forbidden
            string msg = ex.Message;
        }

        // SDK Attempt
        try
        {
            this.CallRequestSyncApiWithSdk();
        }
        catch (GoogleApiException ex)
        {
            // Google.Apis.Requests.RequestError
            // The caller does not have permission[403]
            // Errors[Message[The caller does not have permission] Location[- ] Reason[forbidden] Domain[global]]
            //  at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response) in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 243
            //  at Google.Apis.Requests.ClientServiceRequest`1.Execute() in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 167
            string msg = ex.Message;
        }
    }

    private string GetAccessToken()
    {
        string defaultScope = "https://www.googleapis.com/auth/homegraph";
        string serviceAccount = OAuthConstants.GoogleServiceAccountEmail; // "??????@??????.iam.gserviceaccount.com"
        string certificateFile = OAuthConstants.CertificateFileName; // "??????.p12"
        var oAuth2 = new GoogleOAuth2(defaultScope, serviceAccount, certificateFile); // As per https://stackoverflow.com/questions/26478694/how-to-produce-jwt-with-google-oauth2-compatible-algorithm-rsa-sha-256-using-sys
        bool status = oAuth2.RequestAccessTokenAsync().Result;

        // This access token at a glance appears invalid due to an empty header and payload,
        // But verifies ok when tested here: https://accounts.google.com/o/oauth2/tokeninfo?access_token=
        return oAuth2.AccessToken;
    }

    private string CallRequestSyncApiManually(string accessToken, string url)
    {
        string apiRequestBody = @"{""agentUserId"": """ + OAuthConstants.TestAgentUserId + @"""}";
        var client = new HttpClient();
        var request = (HttpWebRequest)WebRequest.Create(url);
        var data = Encoding.ASCII.GetBytes(apiRequestBody);
        request.Method = "POST";
        request.Accept = "application/json";
        request.ContentType = "application/json";
        request.ContentLength = data.Length;
        request.Headers.Add("Authorization", $"Bearer {accessToken}");
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        using (var stream = request.GetRequestStream())
        {
            stream.Write(data, 0, data.Length);
        }

        var response = (HttpWebResponse)request.GetResponse();
        var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

        return responseString;
    }

    private void CallRequestSyncApiWithSdk()
    {
        var certificate = new X509Certificate2(OAuthConstants.CertificateFileName, OAuthConstants.CertSecret, X509KeyStorageFlags.Exportable);

        var credential = new ServiceAccountCredential(
           new ServiceAccountCredential.Initializer(OAuthConstants.GoogleServiceAccountEmail)
           {
                   Scopes = new[] { "https://www.googleapis.com/auth/homegraph" },
           }.FromCertificate(certificate));

        var service = new HomeGraphServiceService(
            new BaseClientService.Initializer()
            {
                // Complains if API key is not provided, even though we're using a certificate from a Service Account
                ApiKey = OAuthConstants.GoogleApiKey,
                HttpClientInitializer = credential,
                ApplicationName = OAuthConstants.ApplicationName,
            });

        var request = new RequestSyncRequest(
            service,
            new RequestSyncDevicesRequest
            {
                AgentUserId = OAuthConstants.TestAgentUserId
            });

        request.Execute();
    }
}

Конфигурация учетной записи

Скриншоты учетной записи. (Мне пока не разрешено публиковать изображения, поэтому они являются ссылками)

HomeGraph включен

Мой ключ API неограничен

В моей учетной записи службы включен создатель токенов владельца и учетной записи службы

Обновления

Я попытался пропустить получение маркера доступа вручную, согласно предложению Devunwired. Несмотря на то, что это устраняет ошибку, которую я получал, когда не предоставлял ключ API, я все равно получаю 403. Моя причина для выполнения части маркера доступа вручную была частью отладки 403, которую я получал с помощью вызова API. Таким образом, я мог, по крайней мере, увидеть, как часть процесса работает. Я счастлив использовать библиотечную версию для решения, так как токен доступа не является проблемой.

public void GoogleLibraryJsonCredentialExample()
{
    try
    {
        GoogleCredential credential;

        using (var stream = new FileStream(OAuthConstants.JsonCredentialsFileName, FileMode.Open, FileAccess.Read))
        {
            credential = GoogleCredential.FromStream(stream).CreateScoped(new[] { OAuthConstants.GoogleScope });
        }

        var service = new HomeGraphServiceService(
            new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = OAuthConstants.ApplicationName,
            });

        var request = new RequestSyncRequest(
            service,
            new RequestSyncDevicesRequest
            {
                AgentUserId = OAuthConstants.TestAgentUserId
            });

        request.Execute();
    }
    catch (Exception ex)
    {
        // Receive 403, Forbidden
        string msg = ex.Message;
    }
}

Проблемы

Возможно ли, что мне нужно сделать вызов API из проверенного домена или домена из белого списка? В данный момент я запускаю его из консольного приложения, запущенного на моей машине для разработки. Насколько я понимаю, проверка домена не относится к входящим вызовам и поэтому не должна быть проблемой.

Ответы [ 2 ]

1 голос
/ 31 января 2020

Я выполняю, как представляется, успешные запросы на токен доступа.

Вам не нужно вручную запрашивать маркеры доступа OAuth при использовании клиентских библиотек Google. Обычно они обрабатывают этот процесс внутренне, используя учетные данные, которые вы предоставляете с консоли GCP.

Хотя в документации это не показано, я видел, что некоторые люди используют ключ API. В самом деле, его обязательно нужно включать в подход SDK.

Мы не рекомендуем использовать метод API-ключа для доступа к Home Graph API. Вы должны использовать учетные данные учетной записи службы. Ключи API будут технически работать для метода Request Syn c, но вы не сможете аутентифицировать Состояние отчета с помощью ключа API.

Тот факт, что Вы получаете сообщение об ошибке, пытаясь создать HomeGraphServiceService без ключа API, что может указывать на то, что используемые учетные данные установлены неправильно (нет закрытого ключа или, возможно, отсутствуют области). Рекомендуемый способ предоставления учетных данных учетной записи службы - загрузить их в формате JSON, а не в сертификате, а код для создания учетных данных из JSON должен выглядеть примерно так:

GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
    credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}

Вы можете Найдите дополнительные C# примеры для аутентификации API в руководстве по аутентификации .

0 голосов
/ 04 февраля 2020

Проблема не была связана с моим разрешением поговорить с HomeGraph API или этим пользователем. Вместо этого именно здесь HomeGraph хотел вызвать мое умное домашнее действие, но токен доступа истек. При попытке переосмыслить sh токен, ошибочная реализация с моей стороны привела к тупому 403, который Google затем возвращал мне. дата для токена, который никогда не должен истечь, я установил для него DateTime.MaxValue (впоследствии был отправлен через некоторую дальнейшую обработку). К сожалению, когда это, наконец, приведено к типу int, это значение превышает int.Max. Последующее время истечения было установлено как эпоха (то есть в прошлом), и поэтому проверка токена не удалась из-за истечения срока. значение, указанное в выходной полезной нагрузке SYN C. В моем случае я проверил это.

Большое спасибо всем, кто смотрел на это.

...