【问题标题】:Teams Bot throws Unauthorized error when attempting to send message back尝试发回消息时,Teams Bot 引发未经授权的错误
【发布时间】:2020-03-03 18:08:24
【问题描述】:

我正在尝试使用 Python 机器人框架 SDK 构建一个简单的 MS Teams 机器人。使用模拟器在本地测试我的机器人时,一切正常。我使用旧版门户在此处https://dev.botframework.com/bots 注册了机器人,因为我不想创建 Azure 订阅。

我将应用程序 ID 和应用程序密码添加到机器人并使用 API 网关(具有 HTTP 代理集成)将其部署在 EC2 机器上,以获取消息传递端点的 HTTPS url。

部署后,代码能够接收和解析来自开发框架页面上的测试功能和 Teams 上实际部署的应用程序的消息。但是,当我尝试回复该消息时,我收到一条未经授权的错误消息。

这是堆栈跟踪:

[on_turn_error] unhandled error: Operation returned an invalid status code 'Unauthorized'
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/bot_adapter.py", line 103, in run_pipeline
    context, callback
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/middleware_set.py", line 69, in receive_activity_with_status
    return await self.receive_activity_internal(context, callback)
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/middleware_set.py", line 79, in receive_activity_internal
    return await callback(context)
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/activity_handler.py", line 28, in on_turn
    await self.on_message_activity(turn_context)
  File "/home/ec2-user/bot/bot.py", line 24, in on_message_activity
    return await turn_context.send_activity(response)
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/turn_context.py", line 165, in send_activity
    result = await self.send_activities([activity_or_text])
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/turn_context.py", line 198, in send_activities
    return await self._emit(self._on_send_activities, output, logic())
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/turn_context.py", line 276, in _emit
    return await logic
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/turn_context.py", line 193, in logic
    responses = await self.adapter.send_activities(self, output)
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/bot_framework_adapter.py", line 444, in send_activities
    raise error
  File "/usr/local/lib/python3.7/site-packages/botbuilder/core/bot_framework_adapter.py", line 431, in send_activities
    activity.conversation.id, activity.reply_to_id, activity
  File "/usr/local/lib/python3.7/site-packages/botframework/connector/aio/operations_async/_conversations_operations_async.py", line 533, in reply_to_activity
    raise models.ErrorResponseException(self._deserialize, response)
botbuilder.schema._models_py3.ErrorResponseException: Operation returned an invalid status code 'Unauthorized'

我的应用代码是:

CONFIG = DefaultConfig()
SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD,
                                       CONFIG.APP_AUTH_TENANT, CONFIG.APP_OAUTH_ENDPOINT)
ADAPTER = BotFrameworkAdapter(SETTINGS)


# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
    print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
    traceback.print_exc()

    # Send a message to the user
    await context.send_activity("The bot encountered an error or bug.")
    await context.send_activity("To continue to run this bot, please fix the bot source code.")
    # Send a trace activity if we're talking to the Bot Framework Emulator
    if context.activity.channel_id == "emulator":
        # Create a trace activity that contains the error object
        trace_activity = Activity(
            label="TurnError",
            name="on_turn_error Trace",
            timestamp=datetime.utcnow(),
            type=ActivityTypes.trace,
            value=f"{error}",
            value_type="https://www.botframework.com/schemas/error",
        )
        # Send a trace activity, which will be displayed in Bot Framework Emulator
        await context.send_activity(trace_activity)


ADAPTER.on_turn_error = on_error
APP_ID = SETTINGS.app_id
dynamodb = boto3.resource("dynamodb")
CONVERSATION_REFERENCES = dynamodb.Table("ConversationReferences")

# Create the Bot
BOT = MyBot(CONVERSATION_REFERENCES)


# Listen for incoming requests on /api/messages
async def messages(req):
    print(f"Message Received - {str(datetime.now())}")
    json_request = await req.json()
    print(f"Request Body: {json_request}")
    activity = Activity().deserialize(json_request)
    print("Request successfully deserialized")
    auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
    try:
        print("Sending activity to adapter")
        response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
        if response:
            return Response(status=response.status, text=response.body)
        return Response(status=201)
    except Exception as exception:
        raise exception

async def health(req):
    return Response(status=200, text="Working")

APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)
APP.router.add_get("/health", health)

if __name__ == "__main__":
    web.run_app(APP)

我的机器人代码是:

class MyBot(ActivityHandler):

    def __init__(self, conversation_references):
        self.conversation_references = conversation_references

    async def on_message_activity(self, turn_context: TurnContext):
        print("Message received by bot adapter")
        # The next two lines also cause an unauthorized error. I commented them out to try and simplify
        # team_details = await teams.TeamsInfo.get_members(turn_context)
        # user = team_details[1].email
        user = "test@test.com"
        conversation = self.conversation_references.get_item(Key={"user": user})
        if "Item" in conversation:
            response = "You are already registered"
        else:
            conversation = TurnContext.get_conversation_reference(turn_context.activity)
            item = {"user": user, "conversation": conversation.as_dict()}
            self.conversation_references.put_item(Item=item)
            response = "You have been successfully registered!"
        return await turn_context.send_activity(response)

更新:在本地测试时,我没有将app id和密码添加到模拟器中。当我这样做时,在调试模式下我收到以下错误消息:“向对话 xxxxxx|livechat 发布“/INSPECT open”命令时发生错误:400:机器人的 Microsoft 应用程序 ID 或 Microsoft 应用程序密码不正确。”

不过,我 100% 确定 id 和密码是正确的,因为我已手动使用注册页面中的令牌端点来获取带有这些凭据的访问令牌。这可能与以下事实有关:在代码中并手动使用端点我可以指定目录(租户)ID,而我不能使用模拟器这样做。

另一个奇怪的点是,当模拟器返回该响应时,它实际上似乎并没有向我的本地端点发出请求,所以我什至不确定 400 响应来自哪里。

【问题讨论】:

  • 我可以看到您正在将适配器配置为使用应用 ID 和密码,但您的机器人配置实际上是否包含要提供给适配器的应用 ID 和密码?当您在本地测试机器人时,您是否将应用 ID 和密码提供给模拟器?在 debugging locally using ngrok 时尝试在 Teams 或 Web Chat 上进行测试。另外,我们是假设这真的是您的整个机器人,还是您实际上是在使用这些对话引用来发送主动消息? Teams 要求您致电 TrustServiceUrl
  • 感谢您的回复,在本地测试时,我没有向模拟器提供应用 ID 和密码。当我这样做时,它告诉我我的应用程序 ID 或密码错误,但我已经三次检查了这些值,甚至要求输入新密码。我会将调试模式下的错误消息添加到问题中。我还在邮递员中手动使用了令牌端点并获得了访问令牌。我将发送带有对话参考的主动消息,但现在我只是想获得最初的电话和对工作的响应。
  • 您的应用程序 ID 和密码在您的机器人的 config.py 中吗?模拟器直接与您的机器人通信,因此您对机器人注册或 AAD 应用程序注册所做的任何配置都不会影响模拟器。如果您的机器人未经身份验证(意味着它没有凭据或未编程为使用凭据),那么这就是为什么您只能在模拟器中连接到它而不指定凭据的原因。你可以阅读here
  • @KyleDelany 我认为除了本地托管的机器人之外,模拟器确实连接到应用程序注册数据,因为初始对话请求被发送到不同的端点。我接受了您提供的链接中信息的建议,即在应用程序注册中从单租户切换到多租户,并解决了身份验证问题。非常感谢您的帮助,如果您想将该链接放入答案中,我很乐意接受。

标签: python amazon-web-services botframework


【解决方案1】:

您需要确保使用多租户 AAD 应用凭据,如 here 所述。

【讨论】:

    【解决方案2】:

    以下是您将从团队获得的内容。 它具有关键的 serviceURL,它将用于连接回您的机器人,该机器人使用以下 json 中可用的其他参数连接回您的团队中,我已将其替换为 > 之间的逻辑名称。

        { text: 'help',
      textFormat: 'plain',
      type: 'message',
      timestamp: 2020-03-05T12:29:26.830Z,
      localTimestamp: 2020-03-05T12:29:26.830Z,
      id: '1583411366810',
      channelId: 'msteams',
      serviceUrl: 'https://smba.trafficmanager.net/emea/',
      from:
       { id:
          '<<Use ID>>',
         name: '<<Display Name>>',
         aadObjectId: '<<objectID>>' },
      conversation:
       { conversationType: 'personal',
         tenantId: '<<Microsoft Tenant ID>>',
         id:
          '<<Unique Conversation ID>>' },
      recipient:
       { id: '<<BotID>>', name: 'Sybot' },
      entities:
       [ { locale: 'en-US',
           country: 'US',
           platform: 'Windows',
           type: 'clientInfo' } ],
      channelData: { tenant: { id: '<<Microsoft Tenant ID>>' } },
      locale: 'en-US' }
    

    收到此消息后,您必须使用以下代码将服务 url 设置为受信任的 url,这样当您将消息发送回用户时,您就不会收到未经授权的错误。

    const { MicrosoftAppCredentials } = require('botbuilder/node_modules/botframework-connector'); 
    const { BotFrameworkAdapter } = require('botbuilder');
    const { TurnContext } = require('botbuilder');
    
        const adapter = new BotFrameworkAdapter({
            appId: <<Your App ID>>,
            appPassword: <<Your App Password>>,
        });
        turnContext = new TurnContext(adapter, contextActivity);
    
        if (!MicrosoftAppCredentials.isTrustedServiceUrl(serviceUrl)) {
            MicrosoftAppCredentials.trustServiceUrl(serviceUrl);
        }
       await context.sendActivity(`Hello World`);
    

    【讨论】:

    • 这看起来更像 Node.js 而不是 Python
    • 是的,这是 node js,但是概念是相似的。我们必须使服务 URL 成为受信任的 URL。
    • @KulandaiGeorgeMariasingaray 我认为该机器人默认信任 Teams 服务 URL。添加此代码的 python 版本并没有改变任何内容。
    猜你喜欢
    • 2021-04-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-14
    • 2021-02-19
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多