【问题标题】:Operation returned an invalid status code 'Forbidden' when calling GetConversationMembersAsync for 1:1 private conversation为 1:1 私人对话调用 GetConversationMembersAsync 时,操作返回了无效状态代码“Forbidden”
【发布时间】:2020-07-07 22:08:47
【问题描述】:

使用此处的“Hello World”Microsoft Teams 应用程序示例:https://github.com/OfficeDev/msteams-samples-hello-world-csharp

在调用来自消息传递扩展程序的操作命令后,尝试获取个人 Microsoft Teams 1:1 聊天的参与者列表。具体来说,我需要其他参与者的电子邮件地址,我是第一个参与者。

这是来自消息控制器的代码:

[BotAuthentication]
public class MessagesController : ApiController
{
    [HttpPost]
    public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
    {
        using (var connector = new ConnectorClient(new Uri(activity.ServiceUrl)))
        {
            if (activity.IsComposeExtensionQuery())
            {
                // Invoke the command handler
                var response = await MessageExtension.HandleMessageExtensionQuery(connector, activity).ConfigureAwait(false);
                return response != null
                    ? Request.CreateResponse<ComposeExtensionResponse>(response)
                    : new HttpResponseMessage(HttpStatusCode.OK);
            }
            else
            {
                await EchoBot.EchoMessage(connector, activity);
                return new HttpResponseMessage(HttpStatusCode.Accepted);
            }
        }
    }
}

MessageExtension.HandleMessageExtensionQuery的代码如下:

    public static async Task<ComposeExtensionResponse> HandleMessageExtensionQuery(ConnectorClient connector, Activity activity)
    {
        var query = activity.GetComposeExtensionQueryData();
        if (query == null)
        {
            return null;
        }

        // Exception thrown here - error 403, there is no additional data except "Operation returned an invalid status code 'Forbidden'"
        var members = await connector.Conversations.GetConversationMembersAsync(activity.Conversation.Id);

        var handler = GetCommandHandler(query.CommandId); // Gets a handler based on the command, irrelevant for this question
        if (handler == null)
        {
            return null;
        }

        return await handler.HandleCommand(query, members); // Should handle the command, but never comes here if we are in a 1:1 conversation
    }

GetConversationMembersAsync 的调用失败并显示以下消息:Operation returned an invalid status code 'Forbidden' 如果该命令是从两个人之间的 1:1 个人对话中调用的。

如果从组频道调用,调用不会失败。

如何获取 1:1 个人对话的参与者列表?为了做到这一点,我是否必须通过机器人对我的用户进行身份验证,或者我是否必须授予我的机器人一些特定的权限?我的帐户是否需要具有某些特定权限才能执行此操作?

编辑 - 添加应用清单

{
    "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
    "manifestVersion": "1.5",
    "version": "1.0.0",
    "id": "1ce95960-0417-4469-ab77-5052758a4e7e",
    "packageName": "com.contoso.helloworld",
    "developer": {
        "name": "Contoso",
        "websiteUrl": "https://8112abe3.ngrok.io",
        "privacyUrl": "https://8112abe3.ngrok.io/privacy-policy",
        "termsOfUseUrl": "https://8112abe3.ngrok.io/terms-service"
    },
    "icons": {
        "color": "color.png",
        "outline": "outline.png"
    },
    "name": {
        "short": "Hello World",
        "full": "Hello World App"
    },
    "description": {
        "short": "Hello World App for Microsoft Teams",
        "full": "This sample app provides a very simple app. You can extend this to add more content and capabilities."
    },
    "accentColor": "#60A18E",
    "configurableTabs": [
        {
            "configurationUrl": "https://526d7c43.ngrok.io/configure",
            "canUpdateConfiguration": true,
            "scopes": [
                "team",
                "groupchat"
            ]
        }
    ],
    "staticTabs": [
        {
            "entityId": "com.contoso.helloworld.hellotab",
            "name": "Hello Tab",
            "contentUrl": "https://8112abe3.ngrok.io/hello",
            "websiteUrl": "https://8112abe3.ngrok.io/hello",
            "scopes": [
                "personal"
            ]
        }
    ],
    "bots": [
        {
            "botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
            "scopes": [
                "personal",
                "team",
                "groupchat"
            ],
            "supportsFiles": false,
            "isNotificationOnly": false
        }
    ],
    "composeExtensions": [
        {
            "botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
            "canUpdateConfiguration": true,
            "commands": [
                {
                    "id": "getRandomText",
                    "type": "query",
                    "title": "Get random text",
                    "description": "",
                    "initialRun": true,
                    "fetchTask": false,
                    "context": [
                        "commandBox",
                        "compose",
                        "message"
                    ],
                    "parameters": [
                        {
                            "name": "cardTitle",
                            "title": "Subject",
                            "description": "",
                            "inputType": "text"
                        }
                    ]
                }
            ]
        }
    ],
    "permissions": [
        "identity",
        "messageTeamMembers"
    ],
    "validDomains": [
        "8112abe3.ngrok.io"
    ]
}

编辑 2 - 尝试后,基于示例 51 - TeamsMessagingExtensionsAction

按照建议,我尝试使用 sample 51 called "TeamsMessagingExtensionsAction" 代码是:

MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
var members = (await turnContext.TurnState.Get<IConnectorClient>().Conversations.GetConversationMembersAsync(
    turnContext.Activity.Conversation.Id).ConfigureAwait(false)).ToList();

堆栈跟踪的异常:

Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'
   at Microsoft.Bot.Connector.Conversations.GetConversationMembersWithHttpMessagesAsync(String conversationId, Dictionary`2 customHeaders, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Connector\Conversations.cs:line 1462
   at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.ShareMessageCommand(ITurnContext`1 turnContext, MessagingExtensionAction action) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 68
   at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 29
   at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTeamsMessagingExtensionSubmitActionDispatchAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 201
   at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnInvokeActivityAsync(ITurnContext`1 turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 88
   at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 39
   at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotFrameworkAdapter.cs:line 1158
   at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\MiddlewareSet.cs:line 55
   at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotAdapter.cs:line 182

EDIT 3 - 尝试使用示例 57

所以我从sample 57 called "TeamsConversationBot" 和 TLDR 开始;在频道中有效,在私人对话中无效。

以下是执行的步骤:
注意:ngrok 已使用命令 ngrok http -host-header=rewrite 3978 启动,并且我现有的机器人注册已配置为侦听给定的 URL

  1. 更新了项目的 appsettings.json 配置,以使用来自 Bot Framework 注册的 Microsoft 应用 ID 和应用密码。
  2. 使用所需的 GUID 编辑 TeamsAppManifest 中包含的 manifest.json,将其与 PNG 图标一起压缩到 manifest.zip 并使用“导入现有应用程序”上传到 Teams App Studio,结果在名为“TeamsConversationBot”的新应用中
  3. 在 Teams App Studio 中,打开新应用程序进行编辑,转到“测试和分发”,单击“安装”,然后单击“添加” strong>”在下一个屏幕上。
  4. 再次导航到上面的屏幕,但我从下拉菜单中选择了“添加到团队”而不是“添加”,我在其中将机器人添加到我所属的团队的频道。
  5. 在 Visual Studio 中,转到 TeamsConversationsBot.cs 并在 MessageAllMembersAsync 方法中设置断点,该行显示为 var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);
  6. 启动项目,进入我在 Teams 中的频道,向我的机器人发送“MessageAllMembers”,等待上述断点被命中并观察到上述调用成功,即返回一个成员名单。 到目前为止一切顺利!
  7. 返回 Teams App Builder 编辑我的“TeamsConversationsBot”应用。
  8. 在清单编辑器中,转到“消息扩展”选项卡并设置我现有的机器人以侦听消息扩展调用。
  9. 添加了一个新的基于操作的命令,该命令具有定义数量的参数。给它一个 ID,命名一个参数并输入其他必填字段,这并不重要。对于“选择撰写扩展应在其中工作的上下文”,我选择了“Command Box”和“Compose Box”,勾选了“Initial run ”并点击“保存”。
  10. 使用“测试和分发”->“安装”然后“添加”按钮再次安装应用程序。
  11. 在 Visual Studio 中,将以下方法添加到 TeamsConversationBot.cs

    protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
    {
        var connector = turnContext.TurnState.Get<IConnectorClient>();
        var conversation = turnContext.Activity.Conversation;
        var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
        return await base.OnInvokeActivityAsync(turnContext, cancellationToken);
    }
    
  12. var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken); 行设置断点并启动项目

  13. 进入频道,在撰写框下选择消息扩展并调用新添加的命令。这触发了上面的断点被击中。执行了一步并观察到members 变量包含通道的所有成员。 这样也行!
  14. 进入我和其他团队成员之间的私人对话(我和他们之前都互相发送过消息,以确保对话不为空),在撰写框下选择消息扩展并调用新添加的命令。这再次触发了上述断点被击中。执行了一步,bam!调用导致了未处理的异常Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'

【问题讨论】:

  • 您是在频道还是个人聊天中尝试这个?如果是频道,那么您能否确认您是否为您正在尝试的团队安装了该应用程序? Bot Id 或 Manifest 将有助于进一步检查。
  • 抱歉问错了,我们正在调查您的问题,同时请您给我们一些时间,我们会尽快回复您...
  • 我们尝试了您的代码,它运行良好,我们最终获得了会员,请您帮忙提供您的机器人 ID 或应用清单,以便我们进一步调查问题
  • 如果您正在寻找参与者的电子邮件 ID,您可以从 activity.From 中获取。
  • @DejanJanjušević 我可以看到这可能是TrustServiceUrl issue,因为 Trinetra 无法重现。您可以尝试在GetConversationMembersAsync 调用之前添加MicrosoftAppCredentials.trustServiceUrl(activity.ServiceUrl); 吗?

标签: c# botframework microsoft-teams


【解决方案1】:

对修改 3 的回应

抱歉,我好像误解了你的意图。您无法使用 Bot Framework 执行此操作。从技术上讲,该机器人不是您与其他用户之间的个人对话的一部分。因此,它无权获取有关该对话的信息,即使它可以作为消息扩展程序使用。

您也许可以使用this Graph API call,但您必须自己处理获取身份验证令牌:

https://graph.microsoft.com/beta/chats/<conversationId>/members

将此作为答案是因为评论太长了,我认为它应该可以满足您的需求。请让我知道这是否有效,我可以编辑它


通过查看您的 appId,我没有在后端看到任何内容,因此我不确定问题的实际原因是什么。

您根本不需要设置任何权限——这些权限仅适用于 OAuth,GetConversationMembersAsync 不需要。您应该只需要在您的manifest.json 中设置的范围,这些范围似乎设置得很好。

但是,对于您的应用注册,您可能需要确保在身份验证下选中此项:

如果不是,您可以手动调整 应用注册的清单,方法是设置:

"signInAudience": "AzureADandPersonalMicrosoftAccount",

如果这些都不起作用:

请尝试Sample 57,它有一个GetMembersAsync 函数可以完成同样的事情,但使用的是当前的BotFramework SDK。请务必遵循自述文件并按照指定设置所有内容。

让我知道进展如何。如果您需要其他帮助,您能否尝试尽可能详细地解释您的复制步骤?我想尝试重现这一点,并希望确保我们的步骤匹配——它还可以帮助我找出你可能出错的地方。

【讨论】:

  • 谢谢。 signInAudience 已设置为 "AzureADandPersonalMicrosoftAccount"。我将尝试使用示例 57,并告诉你它是如何进行的。
  • 还是同样的问题...请看看我对 OP 的编辑#3
  • @DejanJanjušević 感谢您提供详细的复制步骤!原来我直到现在才理解你的问题。我在当前答案的顶部编辑了更新的答案。很抱歉无法完成。
  • @DejanJanjušević 我的错误。尽管您的描述很清楚,但我错误地解释为“与机器人进行 1:1 对话”。
【解决方案2】:

实际上,在调用基于操作的消息传递扩展程序时,有一种方法可以获取两个人之间 1:1 私人对话的参与者列表。诀窍是让机器人也进入私人对话。

我一开始就考虑过这个解决方案,但不知道怎么做,而且由于没有人建议,我认为不可能一次性完成。但事实证明我错了。

因此,以防万一有人偶然发现此类问题。在这里。

最初我使用 FetchTask 操作消息传递扩展(基于操作的消息传递扩展,您不提供预定义的参数列表,但使用机器人获取参数)来完成此操作。

但是也可以使用带有静态参数的常规操作来完成,所以让我们使用我在问题中使用的方法OnInvokeActivityAsync 来做一个例子。请参阅下面代码中的 cmets 进行解释。

protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
    var connector = turnContext.TurnState.Get<IConnectorClient>();
    var conversation = turnContext.Activity.Conversation;
    IList<ChannelAccount> members;

    try
    {
        members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
    }
    catch (ErrorResponseException ex)
    {
        // If the ErrorResponseException contains the response with status code 403, that means our bot is not a member of this conversation.
        // In that case, return an adaptive card containing the prompt to add the bot to the current conversation.
        // After accepting the prompt, the bot will be added to the conversation and we will be able to obtain the list of conversation participants.
        if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
        {
            return new InvokeResponse
            {
                Status = 200,
                Body = AddBotToConversation()
            };
        }

        throw;
    }

    // At this point, we have the list of conversation members
    var otherMember = members.FirstOrDefault(x => x.Id != turnContext.Activity.From.Id);

    return new InvokeResponse
    {
        Status = 200,
        Body = await DoSomethingWithOtherMemberInformationAndReturnACard(otherMember, cancellationToken)
    };
}

AddBotToConversation 可以这样定义:

private MessagingExtensionActionResponse AddBotToConversation()
{
    var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
    {
        Body = new List<AdaptiveElement>()
        {
            new AdaptiveTextBlock("We need to add the bot to this conversation in order to perform the requested action"),
        },
        Actions = new List<AdaptiveAction>()
        {
            new AdaptiveSubmitAction
            {
                Title = "Continue",
                Data = new Dictionary<string, object>
                {
                    // The magic happens here. This tells Teams to add this bot to the current conversation
                    ["msteams"] = new Dictionary<string, bool>
                    {
                        ["justInTimeInstall"] = true,
                    }
                }
            }
        }
    };

    var invokeResponse = new MessagingExtensionActionResponse
    {
        Task = new TaskModuleContinueResponse
        {
            Value = new TaskModuleTaskInfo
            {
                Card = new Attachment
                {
                    ContentType = AdaptiveCard.ContentType,
                    Content = card
                }
            }
        }
    };

    return invokeResponse;
}

只需确保消息传递扩展程序是基于操作的,而不是基于搜索的。搜索扩展将不支持这种方法。

编辑最后但同样重要的是,不要忘记将 "groupchat" 范围添加到 Teams 应用 manifest.json 文件中 "bots" 集合下的机器人的 "scopes" 集合,否则您将无法将您的机器人添加到私人对话中。

【讨论】:

  • 我同意这是一个很好的解决方法。请小心将任何 Forbidden InvokeResponse 设置为 200,尤其是在您使用 OAuth 进行任何操作时。这不应该是安全风险,但可能会产生一些意想不到的后果。
猜你喜欢
  • 1970-01-01
  • 2021-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多