Проблема
Когда я вызываю «Запрос на синхронизацию» в API Google HomeGraph, я получаю ответ «403 Forbidden».
Фон
Я пишу Smart Home Action и успешно внедрил SYNC, QUERY и EXECUTE. Тестирование на моем мобильном устройстве я могу видеть и взаимодействовать с устройствами в порядке. Сейчас я пытаюсь реализовать Request Sync, но не могу взаимодействовать с 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();
}
}
Конфигурация учетной записи
Скриншоты аккаунта. (мне еще не разрешено публиковать изображения, так что это ссылки)
У моего ключа 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 из проверенного домена или домена из белого списка? На данный момент я запускаю его из консольного приложения, работающего на моем компьютере для разработки. Я понимаю проверку домена так, что она не применяется к входящим вызовам и, следовательно, не должна быть проблемой.