Example #1
0
    async def _send_token_exchange_invoke_to_skill(
        self,
        incoming_activity: Activity,
        request_id: str,
        connection_name: str,
        token: str,
    ):
        activity = incoming_activity.create_reply()
        activity.type = ActivityTypes.invoke
        activity.name = SignInConstants.token_exchange_operation_name
        activity.value = TokenExchangeInvokeRequest(
            id=request_id, token=token, connection_name=connection_name,
        )

        # route the activity to the skill
        skill_info = self.dialog_options.skill
        response = await self.dialog_options.skill_client.post_activity(
            self.dialog_options.bot_id,
            skill_info.app_id,
            skill_info.skill_endpoint,
            self.dialog_options.skill_host_endpoint,
            incoming_activity.conversation.id,
            activity,
        )

        # Check response status: true if success, false if failure
        return response.is_successful_status_code()
Example #2
0
    async def _send_token_exchange_invoke_to_skill(
        self,
        turn_context: TurnContext,
        incoming_activity: Activity,
        identifier: str,
        token: str,
    ) -> bool:
        activity = self._create_reply(incoming_activity)
        activity.type = ActivityTypes.invoke
        activity.name = "signin/tokenExchange"
        activity.value = TokenExchangeInvokeRequest(
            id=identifier,
            token=token,
        )

        # route the activity to the skill
        response = await self._client.post_activity(
            self._from_bot_id,
            self._to_bot_id,
            "http://localhost:3979/api/messages",
            "http://tempuri.org/whatever",
            incoming_activity.conversation.id,
            activity,
        )

        # Check response status: true if success, false if failure
        is_success = int(HTTPStatus.OK) <= response.status <= 299
        message = ("Skill token exchange successful"
                   if is_success else "Skill token exchange failed")

        await turn_context.send_activity(MessageFactory.text(message))

        return is_success
Example #3
0
    async def _send_token_exchange_invoke_to_skill(
        self,
        incoming_activity: Activity,
        resource_id: str,
        token: str,
        connection_name: str,
        target_skill: BotFrameworkSkill,
    ) -> bool:
        activity = incoming_activity.create_reply()
        activity.type = ActivityTypes.invoke
        activity.name = SignInConstants.token_exchange_operation_name
        activity.value = TokenExchangeInvokeRequest(
            id=resource_id, token=token, connection_name=connection_name
        )
        skill_conversation_reference = await self._conversation_id_factory.get_conversation_reference(
            incoming_activity.conversation.id
        )
        activity.conversation = (
            skill_conversation_reference.conversation_reference.conversation
        )
        activity.service_url = (
            skill_conversation_reference.conversation_reference.service_url
        )

        # Route the activity to the skill
        response = await self._skill_client.post_activity_to_skill(
            from_bot_id=self._bot_id,
            to_skill=target_skill,
            service_url=self._skills_config.SKILL_HOST_ENDPOINT,
            activity=activity,
        )

        return 200 <= response.status <= 299
Example #4
0
    async def test_process_activity_with_identity_token_exchange_invoke_response(
            self):
        mock_credential_provider = unittest.mock.create_autospec(
            CredentialProvider)

        settings = BotFrameworkAdapterSettings(
            app_id="bot_id",
            credential_provider=mock_credential_provider,
        )
        adapter = AdapterUnderTest(settings)

        identity = ClaimsIdentity(
            claims={
                AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
                AuthenticationConstants.APP_ID_CLAIM: "bot_id",
                AuthenticationConstants.VERSION_CLAIM: "1.0",
            },
            is_authenticated=True,
        )

        inbound_activity = Activity(
            type=ActivityTypes.invoke,
            name=SignInConstants.token_exchange_operation_name,
            service_url="http://tempuri.org/whatever",
            delivery_mode=DeliveryModes.normal,
            conversation=ConversationAccount(id="conversationId"),
            value=TokenExchangeInvokeRequest(
                id="token_exchange_id",
                token="token",
                connection_name="connection_name",
            ),
        )

        async def callback(context: TurnContext):
            activity = Activity(
                type=ActivityTypes.invoke_response,
                value=InvokeResponse(
                    status=200,
                    body=TokenExchangeInvokeResponse(
                        id=context.activity.value.id,
                        connection_name=context.activity.value.connection_name,
                    ),
                ),
            )

            await context.send_activity(activity)

        invoke_response = await adapter.process_activity_with_identity(
            inbound_activity,
            identity,
            callback,
        )

        assert invoke_response
        assert invoke_response.status == 200
        assert invoke_response.body["id"] == inbound_activity.value.id
        assert (invoke_response.body["connectionName"] ==
                inbound_activity.value.connection_name)
    async def _exchanged_token(self, turn_context: TurnContext) -> bool:
        token_exchange_response: TokenResponse = None
        aux_dict = {}
        if turn_context.activity.value:
            for prop in ["id", "connection_name", "token", "properties"]:
                aux_dict[prop] = turn_context.activity.value.get(prop)
        token_exchange_request = TokenExchangeInvokeRequest(
            id=aux_dict["id"],
            connection_name=aux_dict["connection_name"],
            token=aux_dict["token"],
            properties=aux_dict["properties"],
        )
        try:
            adapter = turn_context.adapter
            if isinstance(turn_context.adapter, ExtendedUserTokenProvider):
                token_exchange_response = await adapter.exchange_token(
                    turn_context,
                    self._oauth_connection_name,
                    turn_context.activity.from_property.id,
                    TokenExchangeRequest(token=token_exchange_request.token),
                )
            else:
                raise Exception(
                    "Not supported: Token Exchange is not supported by the current adapter."
                )
        except:
            traceback.print_exc()
        if not token_exchange_response or not token_exchange_response.token:
            # The token could not be exchanged (which could be due to a consent requirement)
            # Notify the sender that PreconditionFailed so they can respond accordingly.

            invoke_response = TokenExchangeInvokeResponse(
                id=token_exchange_request.id,
                connection_name=self._oauth_connection_name,
                failure_detail="The bot is unable to exchange token. Proceed with regular login.",
            )

            await self._send_invoke_response(
                turn_context, invoke_response, HTTPStatus.PRECONDITION_FAILED
            )

            return False

        return True
    async def _recognize_token(
            self, dialog_context: DialogContext) -> PromptRecognizerResult:
        context = dialog_context.context
        token = None
        if OAuthPrompt._is_token_response_event(context):
            token = context.activity.value

            # fixup the turnContext's state context if this was received from a skill host caller
            state: CallerInfo = dialog_context.active_dialog.state[
                OAuthPrompt.PERSISTED_CALLER]
            if state:
                # set the ServiceUrl to the skill host's Url
                dialog_context.context.activity.service_url = state.caller_service_url

                # recreate a ConnectorClient and set it in TurnState so replies use the correct one
                if not isinstance(context.adapter, ConnectorClientBuilder):
                    raise TypeError(
                        "OAuthPrompt: IConnectorClientProvider interface not implemented by the current adapter"
                    )

                connector_client_builder: ConnectorClientBuilder = context.adapter
                claims_identity = context.turn_state.get(
                    BotAdapter.BOT_IDENTITY_KEY)
                connector_client = await connector_client_builder.create_connector_client(
                    dialog_context.context.activity.service_url,
                    claims_identity,
                    state.scope,
                )

                context.turn_state[
                    BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = connector_client

        elif OAuthPrompt._is_teams_verification_invoke(context):
            code = context.activity.value["state"]
            try:
                token = await self.get_user_token(context, code)
                if token is not None:
                    await context.send_activity(
                        Activity(
                            type="invokeResponse",
                            value=InvokeResponse(int(HTTPStatus.OK)),
                        ))
                else:
                    await context.send_activity(
                        Activity(
                            type="invokeResponse",
                            value=InvokeResponse(int(HTTPStatus.NOT_FOUND)),
                        ))
            except Exception:
                await context.send_activity(
                    Activity(
                        type="invokeResponse",
                        value=InvokeResponse(
                            int(HTTPStatus.INTERNAL_SERVER_ERROR)),
                    ))
        elif self._is_token_exchange_request_invoke(context):
            if isinstance(context.activity.value, dict):
                context.activity.value = TokenExchangeInvokeRequest(
                ).from_dict(context.activity.value)

            if not (context.activity.value and self._is_token_exchange_request(
                    context.activity.value)):
                # Received activity is not a token exchange request.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_REQUEST),
                        "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value."
                        " This is required to be sent with the InvokeActivity.",
                    ))
            elif (context.activity.value.connection_name !=
                  self._settings.connection_name):
                # Connection name on activity does not match that of setting.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_REQUEST),
                        "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a"
                        " ConnectionName that does not match the ConnectionName expected by the bots active"
                        " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid"
                        " ConnectionName in the TokenExchangeInvokeRequest",
                    ))
            elif not getattr(context.adapter, "exchange_token"):
                # Token Exchange not supported in the adapter.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_GATEWAY),
                        "The bot's BotAdapter does not support token exchange operations."
                        " Ensure the bot's Adapter supports the ExtendedUserTokenProvider interface.",
                    ))

                raise AttributeError(
                    "OAuthPrompt._recognize_token(): not supported by the current adapter."
                )
            else:
                # No errors. Proceed with token exchange.
                extended_user_token_provider: ExtendedUserTokenProvider = context.adapter

                token_exchange_response = None
                try:
                    token_exchange_response = await extended_user_token_provider.exchange_token_from_credentials(
                        context,
                        self._settings.oath_app_credentials,
                        self._settings.connection_name,
                        context.activity.from_property.id,
                        TokenExchangeRequest(
                            token=context.activity.value.token),
                    )
                except:
                    # Ignore Exceptions
                    # If token exchange failed for any reason, tokenExchangeResponse above stays null, and
                    # hence we send back a failure invoke response to the caller.
                    pass

                if not token_exchange_response or not token_exchange_response.token:
                    await context.send_activity(
                        self._get_token_exchange_invoke_response(
                            int(HTTPStatus.PRECONDITION_FAILED),
                            "The bot is unable to exchange token. Proceed with regular login.",
                        ))
                else:
                    await context.send_activity(
                        self._get_token_exchange_invoke_response(
                            int(HTTPStatus.OK), None,
                            context.activity.value.id))
                    token = TokenResponse(
                        channel_id=token_exchange_response.channel_id,
                        connection_name=token_exchange_response.
                        connection_name,
                        token=token_exchange_response.token,
                        expiration=None,
                    )
        elif context.activity.type == ActivityTypes.message and context.activity.text:
            match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text)
            if match:
                token = await self.get_user_token(context, match[0])

        return (PromptRecognizerResult(True, token)
                if token is not None else PromptRecognizerResult())
Example #7
0
    async def _recognize_token(self,
                               context: TurnContext) -> PromptRecognizerResult:
        token = None
        if OAuthPrompt._is_token_response_event(context):
            token = context.activity.value
        elif OAuthPrompt._is_teams_verification_invoke(context):
            code = context.activity.value["state"]
            try:
                token = await self.get_user_token(context, code)
                if token is not None:
                    await context.send_activity(
                        Activity(
                            type="invokeResponse",
                            value=InvokeResponse(int(HTTPStatus.OK)),
                        ))
                else:
                    await context.send_activity(
                        Activity(
                            type="invokeResponse",
                            value=InvokeResponse(int(HTTPStatus.NOT_FOUND)),
                        ))
            except Exception:
                await context.send_activity(
                    Activity(
                        type="invokeResponse",
                        value=InvokeResponse(
                            int(HTTPStatus.INTERNAL_SERVER_ERROR)),
                    ))
        elif self._is_token_exchange_request_invoke(context):
            if isinstance(context.activity.value, dict):
                context.activity.value = TokenExchangeInvokeRequest(
                ).from_dict(context.activity.value)

            if not (context.activity.value and self._is_token_exchange_request(
                    context.activity.value)):
                # Received activity is not a token exchange request.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_REQUEST),
                        "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value."
                        " This is required to be sent with the InvokeActivity.",
                    ))
            elif (context.activity.value.connection_name !=
                  self._settings.connection_name):
                # Connection name on activity does not match that of setting.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_REQUEST),
                        "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a"
                        " ConnectionName that does not match the ConnectionName expected by the bots active"
                        " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid"
                        " ConnectionName in the TokenExchangeInvokeRequest",
                    ))
            elif not getattr(context.adapter, "exchange_token"):
                # Token Exchange not supported in the adapter.
                await context.send_activity(
                    self._get_token_exchange_invoke_response(
                        int(HTTPStatus.BAD_GATEWAY),
                        "The bot's BotAdapter does not support token exchange operations."
                        " Ensure the bot's Adapter supports the ITokenExchangeProvider interface.",
                    ))

                raise AttributeError(
                    "OAuthPrompt.recognize(): not supported by the current adapter."
                )
            else:
                # No errors. Proceed with token exchange.
                extended_user_token_provider: ExtendedUserTokenProvider = context.adapter
                token_exchange_response = await extended_user_token_provider.exchange_token_from_credentials(
                    context,
                    self._settings.oath_app_credentials,
                    self._settings.connection_name,
                    context.activity.from_property.id,
                    TokenExchangeRequest(token=context.activity.value.token),
                )

                if not token_exchange_response or not token_exchange_response.token:
                    await context.send_activity(
                        self._get_token_exchange_invoke_response(
                            int(HTTPStatus.CONFLICT),
                            "The bot is unable to exchange token. Proceed with regular login.",
                        ))
                else:
                    await context.send_activity(
                        self._get_token_exchange_invoke_response(
                            int(HTTPStatus.OK), None,
                            context.activity.value.id))
                    token = TokenResponse(
                        channel_id=token_exchange_response.channel_id,
                        connection_name=token_exchange_response.
                        connection_name,
                        token=token_exchange_response.token,
                        expiration=None,
                    )
        elif context.activity.type == ActivityTypes.message and context.activity.text:
            match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text)
            if match:
                token = await self.get_user_token(context, match[0])

        return (PromptRecognizerResult(True, token)
                if token is not None else PromptRecognizerResult())