Skip to content

Axsuarez/protocol test project #464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 5, 2019
Merged
79 changes: 67 additions & 12 deletions libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
from botframework.connector import Channels, EmulatorApiClient
from botframework.connector.aio import ConnectorClient
from botframework.connector.auth import (
AuthenticationConfiguration,
AuthenticationConstants,
ChannelValidation,
ChannelProvider,
ClaimsIdentity,
GovernmentChannelValidation,
GovernmentConstants,
MicrosoftAppCredentials,
JwtTokenValidation,
SimpleCredentialProvider,
SkillValidation,
)
from botframework.connector.token_api import TokenApiClient
from botframework.connector.token_api.models import TokenStatus
Expand All @@ -37,6 +41,7 @@
USER_AGENT = f"Microsoft-BotFramework/3.1 (BotBuilder Python/{__version__})"
OAUTH_ENDPOINT = "https://api.botframework.com"
US_GOV_OAUTH_ENDPOINT = "https://api.botframework.azure.us"
BOT_IDENTITY_KEY = "BotIdentity"


class TokenExchangeState(Model):
Expand Down Expand Up @@ -72,13 +77,17 @@ def __init__(
oauth_endpoint: str = None,
open_id_metadata: str = None,
channel_service: str = None,
channel_provider: ChannelProvider = None,
auth_configuration: AuthenticationConfiguration = None,
):
self.app_id = app_id
self.app_password = app_password
self.channel_auth_tenant = channel_auth_tenant
self.oauth_endpoint = oauth_endpoint
self.open_id_metadata = open_id_metadata
self.channel_service = channel_service
self.channel_provider = channel_provider
self.auth_configuration = auth_configuration or AuthenticationConfiguration()


class BotFrameworkAdapter(BotAdapter, UserTokenProvider):
Expand All @@ -90,6 +99,7 @@ def __init__(self, settings: BotFrameworkAdapterSettings):
self.settings.channel_service = self.settings.channel_service or os.environ.get(
AuthenticationConstants.CHANNEL_SERVICE
)

self.settings.open_id_metadata = (
self.settings.open_id_metadata
or os.environ.get(AuthenticationConstants.BOT_OPEN_ID_METADATA_KEY)
Expand Down Expand Up @@ -163,7 +173,7 @@ async def create_conversation(

# Create conversation
parameters = ConversationParameters(bot=reference.bot)
client = self.create_connector_client(reference.service_url)
client = await self.create_connector_client(reference.service_url)

# Mix in the tenant ID if specified. This is required for MS Teams.
if reference.conversation is not None and reference.conversation.tenant_id:
Expand Down Expand Up @@ -207,8 +217,9 @@ async def process_activity(self, req, auth_header: str, logic: Callable):
activity = await self.parse_request(req)
auth_header = auth_header or ""

await self.authenticate_request(activity, auth_header)
identity = await self.authenticate_request(activity, auth_header)
context = self.create_context(activity)
context.turn_state[BOT_IDENTITY_KEY] = identity

# Fix to assign tenant_id from channelData to Conversation.tenant_id.
# MS Teams currently sends the tenant ID in channelData and the correct behavior is to expose
Expand All @@ -228,7 +239,9 @@ async def process_activity(self, req, auth_header: str, logic: Callable):

return await self.run_pipeline(context, logic)

async def authenticate_request(self, request: Activity, auth_header: str):
async def authenticate_request(
self, request: Activity, auth_header: str
) -> ClaimsIdentity:
"""
Allows for the overriding of authentication in unit tests.
:param request:
Expand All @@ -240,11 +253,14 @@ async def authenticate_request(self, request: Activity, auth_header: str):
auth_header,
self._credential_provider,
self.settings.channel_service,
self.settings.auth_configuration,
)

if not claims.is_authenticated:
raise Exception("Unauthorized Access. Request is not authorized")

return claims

def create_context(self, activity):
"""
Allows for the overriding of the context object in unit tests and derived adapters.
Expand Down Expand Up @@ -306,7 +322,8 @@ async def update_activity(self, context: TurnContext, activity: Activity):
:return:
"""
try:
client = self.create_connector_client(activity.service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(activity.service_url, identity)
return await client.conversations.update_activity(
activity.conversation.id, activity.id, activity
)
Expand All @@ -324,7 +341,8 @@ async def delete_activity(
:return:
"""
try:
client = self.create_connector_client(reference.service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(reference.service_url, identity)
await client.conversations.delete_activity(
reference.conversation.id, reference.activity_id
)
Expand Down Expand Up @@ -365,7 +383,10 @@ async def send_activities(
"BotFrameworkAdapter.send_activity(): conversation.id can not be None."
)

client = self.create_connector_client(activity.service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(
activity.service_url, identity
)
if activity.type == "trace" and activity.channel_id != "emulator":
pass
elif activity.reply_to_id:
Expand Down Expand Up @@ -409,7 +430,8 @@ async def delete_conversation_member(
)
service_url = context.activity.service_url
conversation_id = context.activity.conversation.id
client = self.create_connector_client(service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(service_url, identity)
return await client.conversations.delete_conversation_member(
conversation_id, member_id
)
Expand Down Expand Up @@ -446,7 +468,8 @@ async def get_activity_members(self, context: TurnContext, activity_id: str):
)
service_url = context.activity.service_url
conversation_id = context.activity.conversation.id
client = self.create_connector_client(service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(service_url, identity)
return await client.conversations.get_activity_members(
conversation_id, activity_id
)
Expand Down Expand Up @@ -474,7 +497,8 @@ async def get_conversation_members(self, context: TurnContext):
)
service_url = context.activity.service_url
conversation_id = context.activity.conversation.id
client = self.create_connector_client(service_url)
identity: ClaimsIdentity = context.turn_state.get(BOT_IDENTITY_KEY)
client = await self.create_connector_client(service_url, identity)
return await client.conversations.get_conversation_members(conversation_id)
except Exception as error:
raise error
Expand All @@ -488,7 +512,7 @@ async def get_conversations(self, service_url: str, continuation_token: str = No
:param continuation_token:
:return:
"""
client = self.create_connector_client(service_url)
client = await self.create_connector_client(service_url)
return await client.conversations.get_conversations(continuation_token)

async def get_user_token(
Expand Down Expand Up @@ -595,13 +619,44 @@ async def get_aad_tokens(
user_id, connection_name, context.activity.channel_id, resource_urls
)

def create_connector_client(self, service_url: str) -> ConnectorClient:
async def create_connector_client(
self, service_url: str, identity: ClaimsIdentity = None
) -> ConnectorClient:
"""
Allows for mocking of the connector client in unit tests.
:param service_url:
:param identity:
:return:
"""
client = ConnectorClient(self._credentials, base_url=service_url)
if identity:
bot_app_id_claim = identity.claims.get(
AuthenticationConstants.AUDIENCE_CLAIM
) or identity.claims.get(AuthenticationConstants.APP_ID_CLAIM)

credentials = None
if bot_app_id_claim and SkillValidation.is_skill_claim(identity.claims):
scope = JwtTokenValidation.get_app_id_from_claims(identity.claims)

password = await self._credential_provider.get_app_password(
bot_app_id_claim
)
credentials = MicrosoftAppCredentials(
bot_app_id_claim, password, oauth_scope=scope
)
if (
self.settings.channel_provider
and self.settings.channel_provider.is_government()
):
credentials.oauth_endpoint = (
GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
)
credentials.oauth_scope = (
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
)
else:
credentials = self._credentials

client = ConnectorClient(credentials, base_url=service_url)
client.config.add_user_agent(USER_AGENT)
return client

Expand Down
16 changes: 16 additions & 0 deletions libraries/botbuilder-core/botbuilder/core/integration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------

from .aiohttp_channel_service import aiohttp_channel_service_routes
from .bot_framework_http_client import BotFrameworkHttpClient
from .channel_service_handler import ChannelServiceHandler

__all__ = [
"aiohttp_channel_service_routes",
"BotFrameworkHttpClient",
"ChannelServiceHandler",
]
Loading