DialogFlow/Actions:允许 Google 助理用户通过 Actions App 在 Google 日历中创建事件
DialogFlow/Actions: Allow Google Assistant user to create Event in Google Calendar from Actions App
Target/Summary:
我有一个在 Google DialogFlow 中开发的 Actions 应用程序,我希望用户能够使用该应用程序(来自 Google 助手)创建 Google 日历事件。换句话说,对用户进行身份验证以允许 my 应用程序使用 his 日历创建事件。
做了什么:
- 由于 Google Actions 不允许使用 Google Auth/Token 端点,我选择使用 http://www.auth0.com.
- 在 auth0.com 上创建了一个帐户(使用我的 Google 帐户),创建了一个应用程序并使用他们的管理面板设置了以下值(Domain, CliendId and ClientSecret auth0生成):
- 在 Google 云控制台凭据页面上创建了 OAuth 客户端 ID:
- 已配置的操作帐户链接如下:
- 返回 auth0.com > 连接 > 社交 > 启用 Google:
- 在 DialogFlow > 集成 > Google 助手中检查了 "Sign in required":
在我的 DialogFlow Webhook 方法的第一行写入日志以记录以下响应:
{
"originalRequest":{
"source":"google",
"version":"2",
"data":{
"isInSandbox":true,
"surface":{
"capabilities":[
{
"name":"actions.capability.AUDIO_OUTPUT"
},
{
"name":"actions.capability.WEB_BROWSER"
},
{
"name":"actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name":"actions.capability.SCREEN_OUTPUT"
}
]
},
"inputs":[
{
"rawInputs":[
{
"query":"test",
"inputType":"KEYBOARD"
}
],
"arguments":[
{
"rawText":"test",
"textValue":"test",
"name":"text"
}
],
"intent":"actions.intent.TEXT"
}
],
"user":{
"lastSeen":"2018-05-03T11:40:57Z",
"accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx",
"locale":"en-US",
"userId":"15229245xxxxx"
},
"conversation":{
"conversationId":"15253476xxxxx",
"type":"ACTIVE",
"conversationToken":"[\"authentication\",\"wh_patient-details\"]"
},
"availableSurfaces":[
{
"capabilities":[
{
"name":"actions.capability.AUDIO_OUTPUT"
},
{
"name":"actions.capability.SCREEN_OUTPUT"
}
]
}
]
}
},
"id":"1d6ed865-0803-49ca-bbac-xxxx",
"timestamp":"2018-05-03T11:42:22.835Z",
"lang":"en-us",
"result":{
"source":"agent",
"resolvedQuery":"test",
"speech":"",
"action":"v00.xxxxx",
"actionIncomplete":false,
"parameters":{
"CallEnum":"Test"
},
"contexts":[
{
"name":"authentication",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":1
},
{
"name":"actions_capability_screen_output",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":0
},
{
"name":"actions_capability_audio_output",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":0
},
{
"name":"wh_patient-details",
"parameters":{
"patientId":0,
"CallEnum":"Test",
"fallbackLifespan":0,
"providerId":0,
"practiceId":0,
"CallEnum.original":"",
"fullDob":"01 January, 0001"
},
"lifespan":199
},
{
"name":"google_assistant_input_type_keyboard",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":0
},
{
"name":"actions_capability_web_browser",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":0
},
{
"name":"actions_capability_media_response_audio",
"parameters":{
"CallEnum":"Test",
"CallEnum.original":""
},
"lifespan":0
}
],
"metadata":{
"intentName":"v00xxxx",
"isResponseToSlotfilling":false,
"intentId":"c7bd9113-d5b4-4312-8851-xxxxxxx",
"webhookUsed":"true",
"webhookForSlotFillingUsed":"false",
"nluResponseTime":556
},
"fulfillment":{
"speech":"Test",
"messages":[
{
"type":0,
"speech":"Test"
}
]
},
"score":0.8399999737739563
},
"status":{
"code":200,
"errorType":"success"
},
"sessionId":"152534xxxxxxx",
"isStackdriverLoggingEnabled":false
}
相关部分是:
"user":{
"lastSeen":"2018-05-03T11:40:57Z",
"accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx",
"locale":"en-US",
"userId":"15229245xxxxx"
}
- 来自这个post:
The auth token (which you have issued, because you're the OAuth
server) will be sent in the JSON object at
originalRequest.data.user.accessToken.
所以我在下面的代码中使用了上面的授权令牌:
string clientId = "361385932727-ksg6jgjxxxxxSNIP";
string clientSecret = "rc2K1UUyntxxxxxxSNIP";
string accessToken = jsonObject.SelectToken("originalRequest.data.user.accessToken");
string userId = jsonObject.SelectToken("originalRequest.data.user.userId");
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret
},
Scopes = new[] { CalendarService.Scope.Calendar }
});
TokenResponse token = flow.ExchangeCodeForTokenAsync(userId, accessToken,
"https://oauth-redirect.googleusercontent.com/r/xxxxxxxxx", CancellationToken.None).Result;
UserCredential credential = new UserCredential(flow, userId, new TokenResponse { AccessToken = token.AccessToken });
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Test Auth0",
});
var list = service.CalendarList.List().Execute().Items;
例外情况:
Error:"invalid_grant", Description:"Malformed auth code.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
当我将上面代码中的 ClientId/ClientSecret 更改为 auth0.com 中的那个时,异常是:
Error:"invalid_client", Description:"The OAuth client was not found.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
我在这里错过了什么?有人可以帮忙吗
这里是 "missing" 的步骤,之后我让这个东西工作了。我确信必须有其他方法可以做到这一点,但在我在这里得到一些反馈之前,我会这样做就足够了。
注意:Google 操作控制台 UI 已更改,因此原始问题中的屏幕可能不同(但完整)。
- 确保日历 API 在 Google 云控制台(API 库)中针对您选择的项目启用。
返回 https://manage.auth0.com > APIs。编辑列出的系统 API > 机器到机器应用程序。授权您列出的应用程序和 select/tick 以下两个范围:
阅读:用户
read:user_idp_tokens
- 转到 Google 操作控制台 > 帐户链接(参见第 4 点)并检查您是否设置了以下范围:
- 通过以下方式更改您的代码:
一种。使用来自 originalRequest.data.user.accessToken
的授权令牌调用 https://[domain].auth0.com/userinfo(参见第 7 点和第 8 点)。成功的回应应该会给你 userId
b. https://[domain].auth0.com/oauth/token 上的 Post 请求 body 包含您的 client_id、client_secret、观众 grant_type。成功的回复应该会给你一个新的 access_token
.
C。将授权令牌更改为从点 11.b 新获取的 access_token 并调用 https://[domain].auth0.com/api/v2/users/[userId],其中 userId 是您从点 11.a 获得的那个。成功的响应应该给你一个 "Google" access_token 和 userId(在身份下)。
d.将 header 中的授权令牌更改为点 11.c 中的授权令牌。这是您用来调用 Google API 的令牌。例如,对于日历,调用 https://www.googleapis.com/calendar/v3/users/me/calendarList。您将得到所需的答复。
这是代码(我已经重用了变量):
string responseText = string.Empty;
string clientId = "DCuPWHknmv_k2xxxxxxxxxxxxxxxxx"; //Auth0 ClientId
string clientSecret = "7G3xlreFIULPZ9OtwlOxCX99xxxxxxxxxxxxxxxxxxx"; //Auth0 ClientSecret
string accessToken = jsonObject.SelectToken("originalRequest.data.user.accessToken").ToString();
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var url = "https://xxx.auth0.com/userinfo";
responseText = httpClient.GetStringAsync(url).Result;
JObject jsonUserInfo = JObject.Parse(responseText);
string userId = jsonUserInfo.SelectToken("sub").ToString();
url = "https://xxx.auth0.com/oauth/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("audience", "https://[domain].auth0.com/api/v2/"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
var postResult = httpClient.PostAsync(url, content).Result;
jsonUserInfo = JObject.Parse(postResult.Content.ReadAsStringAsync().Result);
accessToken = jsonUserInfo.SelectToken("access_token").ToString();
httpClient.DefaultRequestHeaders.Remove("Authorization");
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
url = "https://xxx.auth0.com/api/v2/users/" + userId;
jsonUserInfo = JObject.Parse(httpClient.GetStringAsync(url).Result);
accessToken = jsonUserInfo.SelectToken("identities[0].access_token").ToString();
userId = jsonUserInfo.SelectToken("identities[0].user_id").ToString();
httpClient.DefaultRequestHeaders.Remove("Authorization");
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
url = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
responseText = httpClient.GetStringAsync(url).Result;
}
}
Target/Summary: 我有一个在 Google DialogFlow 中开发的 Actions 应用程序,我希望用户能够使用该应用程序(来自 Google 助手)创建 Google 日历事件。换句话说,对用户进行身份验证以允许 my 应用程序使用 his 日历创建事件。
做了什么:
- 由于 Google Actions 不允许使用 Google Auth/Token 端点,我选择使用 http://www.auth0.com.
- 在 auth0.com 上创建了一个帐户(使用我的 Google 帐户),创建了一个应用程序并使用他们的管理面板设置了以下值(Domain, CliendId and ClientSecret auth0生成):
- 在 Google 云控制台凭据页面上创建了 OAuth 客户端 ID:
- 已配置的操作帐户链接如下:
- 返回 auth0.com > 连接 > 社交 > 启用 Google:
- 在 DialogFlow > 集成 > Google 助手中检查了 "Sign in required":
在我的 DialogFlow Webhook 方法的第一行写入日志以记录以下响应:
{ "originalRequest":{ "source":"google", "version":"2", "data":{ "isInSandbox":true, "surface":{ "capabilities":[ { "name":"actions.capability.AUDIO_OUTPUT" }, { "name":"actions.capability.WEB_BROWSER" }, { "name":"actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name":"actions.capability.SCREEN_OUTPUT" } ] }, "inputs":[ { "rawInputs":[ { "query":"test", "inputType":"KEYBOARD" } ], "arguments":[ { "rawText":"test", "textValue":"test", "name":"text" } ], "intent":"actions.intent.TEXT" } ], "user":{ "lastSeen":"2018-05-03T11:40:57Z", "accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx", "locale":"en-US", "userId":"15229245xxxxx" }, "conversation":{ "conversationId":"15253476xxxxx", "type":"ACTIVE", "conversationToken":"[\"authentication\",\"wh_patient-details\"]" }, "availableSurfaces":[ { "capabilities":[ { "name":"actions.capability.AUDIO_OUTPUT" }, { "name":"actions.capability.SCREEN_OUTPUT" } ] } ] } }, "id":"1d6ed865-0803-49ca-bbac-xxxx", "timestamp":"2018-05-03T11:42:22.835Z", "lang":"en-us", "result":{ "source":"agent", "resolvedQuery":"test", "speech":"", "action":"v00.xxxxx", "actionIncomplete":false, "parameters":{ "CallEnum":"Test" }, "contexts":[ { "name":"authentication", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":1 }, { "name":"actions_capability_screen_output", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_audio_output", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"wh_patient-details", "parameters":{ "patientId":0, "CallEnum":"Test", "fallbackLifespan":0, "providerId":0, "practiceId":0, "CallEnum.original":"", "fullDob":"01 January, 0001" }, "lifespan":199 }, { "name":"google_assistant_input_type_keyboard", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_web_browser", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 }, { "name":"actions_capability_media_response_audio", "parameters":{ "CallEnum":"Test", "CallEnum.original":"" }, "lifespan":0 } ], "metadata":{ "intentName":"v00xxxx", "isResponseToSlotfilling":false, "intentId":"c7bd9113-d5b4-4312-8851-xxxxxxx", "webhookUsed":"true", "webhookForSlotFillingUsed":"false", "nluResponseTime":556 }, "fulfillment":{ "speech":"Test", "messages":[ { "type":0, "speech":"Test" } ] }, "score":0.8399999737739563 }, "status":{ "code":200, "errorType":"success" }, "sessionId":"152534xxxxxxx", "isStackdriverLoggingEnabled":false
}
相关部分是:
"user":{
"lastSeen":"2018-05-03T11:40:57Z",
"accessToken":"4CfRs-Lt5lWVQuyOYODvf1xxxxxxx",
"locale":"en-US",
"userId":"15229245xxxxx"
}
- 来自这个post:
The auth token (which you have issued, because you're the OAuth server) will be sent in the JSON object at originalRequest.data.user.accessToken.
所以我在下面的代码中使用了上面的授权令牌:
string clientId = "361385932727-ksg6jgjxxxxxSNIP";
string clientSecret = "rc2K1UUyntxxxxxxSNIP";
string accessToken = jsonObject.SelectToken("originalRequest.data.user.accessToken");
string userId = jsonObject.SelectToken("originalRequest.data.user.userId");
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret
},
Scopes = new[] { CalendarService.Scope.Calendar }
});
TokenResponse token = flow.ExchangeCodeForTokenAsync(userId, accessToken,
"https://oauth-redirect.googleusercontent.com/r/xxxxxxxxx", CancellationToken.None).Result;
UserCredential credential = new UserCredential(flow, userId, new TokenResponse { AccessToken = token.AccessToken });
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Test Auth0",
});
var list = service.CalendarList.List().Execute().Items;
例外情况:
Error:"invalid_grant", Description:"Malformed auth code.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
当我将上面代码中的 ClientId/ClientSecret 更改为 auth0.com 中的那个时,异常是:
Error:"invalid_client", Description:"The OAuth client was not found.", Uri:""
Stacktrace: at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.<ExecuteAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<FetchTokenAsync>d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Apis.Auth.OAuth2.Flows.AuthorizationCodeFlow.<ExchangeCodeForTokenAsync>d__30.MoveNext()
我在这里错过了什么?有人可以帮忙吗
这里是 "missing" 的步骤,之后我让这个东西工作了。我确信必须有其他方法可以做到这一点,但在我在这里得到一些反馈之前,我会这样做就足够了。
注意:Google 操作控制台 UI 已更改,因此原始问题中的屏幕可能不同(但完整)。
- 确保日历 API 在 Google 云控制台(API 库)中针对您选择的项目启用。
返回 https://manage.auth0.com > APIs。编辑列出的系统 API > 机器到机器应用程序。授权您列出的应用程序和 select/tick 以下两个范围:
阅读:用户
read:user_idp_tokens
- 转到 Google 操作控制台 > 帐户链接(参见第 4 点)并检查您是否设置了以下范围:
- 通过以下方式更改您的代码:
一种。使用来自originalRequest.data.user.accessToken
的授权令牌调用 https://[domain].auth0.com/userinfo(参见第 7 点和第 8 点)。成功的回应应该会给你userId
b. https://[domain].auth0.com/oauth/token 上的 Post 请求 body 包含您的 client_id、client_secret、观众 grant_type。成功的回复应该会给你一个新的access_token
.
C。将授权令牌更改为从点 11.b 新获取的 access_token 并调用 https://[domain].auth0.com/api/v2/users/[userId],其中 userId 是您从点 11.a 获得的那个。成功的响应应该给你一个 "Google" access_token 和 userId(在身份下)。
d.将 header 中的授权令牌更改为点 11.c 中的授权令牌。这是您用来调用 Google API 的令牌。例如,对于日历,调用 https://www.googleapis.com/calendar/v3/users/me/calendarList。您将得到所需的答复。
这是代码(我已经重用了变量):
string responseText = string.Empty;
string clientId = "DCuPWHknmv_k2xxxxxxxxxxxxxxxxx"; //Auth0 ClientId
string clientSecret = "7G3xlreFIULPZ9OtwlOxCX99xxxxxxxxxxxxxxxxxxx"; //Auth0 ClientSecret
string accessToken = jsonObject.SelectToken("originalRequest.data.user.accessToken").ToString();
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
var url = "https://xxx.auth0.com/userinfo";
responseText = httpClient.GetStringAsync(url).Result;
JObject jsonUserInfo = JObject.Parse(responseText);
string userId = jsonUserInfo.SelectToken("sub").ToString();
url = "https://xxx.auth0.com/oauth/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("audience", "https://[domain].auth0.com/api/v2/"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
var postResult = httpClient.PostAsync(url, content).Result;
jsonUserInfo = JObject.Parse(postResult.Content.ReadAsStringAsync().Result);
accessToken = jsonUserInfo.SelectToken("access_token").ToString();
httpClient.DefaultRequestHeaders.Remove("Authorization");
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
url = "https://xxx.auth0.com/api/v2/users/" + userId;
jsonUserInfo = JObject.Parse(httpClient.GetStringAsync(url).Result);
accessToken = jsonUserInfo.SelectToken("identities[0].access_token").ToString();
userId = jsonUserInfo.SelectToken("identities[0].user_id").ToString();
httpClient.DefaultRequestHeaders.Remove("Authorization");
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
url = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
responseText = httpClient.GetStringAsync(url).Result;
}
}