From 68f6bb5412179cb56f316fb79655f92f6790b850 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 6 Dec 2023 14:30:20 -0600 Subject: [PATCH 01/11] SingleTenant support --- doc/SkillClaimsValidation.md | 8 +++ ...tion_service_client_credentials_factory.py | 19 ------ ...ation_service_client_credential_factory.py | 38 ++++++++++-- .../_built_in_bot_framework_authentication.py | 2 +- ...ameterized_bot_framework_authentication.py | 12 +++- .../connector/auth/app_credentials.py | 2 +- .../auth/authentication_configuration.py | 27 ++++++++ .../auth/authentication_constants.py | 16 +++++ .../connector/auth/jwt_token_validation.py | 2 +- .../auth/microsoft_app_credentials.py | 3 - .../microsoft_government_app_credentials.py | 15 +++-- ...sword_service_client_credential_factory.py | 61 +++++++++---------- .../service_client_credentials_factory.py | 6 +- .../connector/auth/skill_validation.py | 39 ++++++------ 14 files changed, 166 insertions(+), 84 deletions(-) delete mode 100644 libraries/botbuilder-core/botbuilder/core/configuration_service_client_credentials_factory.py diff --git a/doc/SkillClaimsValidation.md b/doc/SkillClaimsValidation.md index ee55c2894..53d761ddf 100644 --- a/doc/SkillClaimsValidation.md +++ b/doc/SkillClaimsValidation.md @@ -48,3 +48,11 @@ ADAPTER = BotFrameworkAdapter( SETTINGS, ) ``` + +For SingleTenant type bots, the additional issuers must be added based on the tenant id: +```python +AUTH_CONFIG = AuthenticationConfiguration( + claims_validator=AllowedSkillsClaimsValidator(CONFIG).claims_validator, + tenant_id=the_tenant_id +) +``` diff --git a/libraries/botbuilder-core/botbuilder/core/configuration_service_client_credentials_factory.py b/libraries/botbuilder-core/botbuilder/core/configuration_service_client_credentials_factory.py deleted file mode 100644 index cd9fbefc5..000000000 --- a/libraries/botbuilder-core/botbuilder/core/configuration_service_client_credentials_factory.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from typing import Any - -from botframework.connector.auth import PasswordServiceClientCredentialFactory - - -class ConfigurationServiceClientCredentialFactory( - PasswordServiceClientCredentialFactory -): - def __init__(self, configuration: Any) -> None: - if not hasattr(configuration, "APP_ID"): - raise Exception("Property 'APP_ID' is expected in configuration object") - if not hasattr(configuration, "APP_PASSWORD"): - raise Exception( - "Property 'APP_PASSWORD' is expected in configuration object" - ) - super().__init__(configuration.APP_ID, configuration.APP_PASSWORD) diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/configuration_service_client_credential_factory.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/configuration_service_client_credential_factory.py index b620e3b68..34e7c7644 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/configuration_service_client_credential_factory.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/configuration_service_client_credential_factory.py @@ -11,8 +11,38 @@ class ConfigurationServiceClientCredentialFactory( PasswordServiceClientCredentialFactory ): def __init__(self, configuration: Any, *, logger: Logger = None) -> None: - super().__init__( - app_id=getattr(configuration, "APP_ID", None), - password=getattr(configuration, "APP_PASSWORD", None), - logger=logger, + app_type = ( + configuration.APP_TYPE + if hasattr(configuration, "APP_TYPE") + else "MultiTenant" ) + app_id = configuration.APP_ID if hasattr(configuration, "APP_ID") else None + app_password = ( + configuration.APP_PASSWORD + if hasattr(configuration, "APP_PASSWORD") + else None + ) + app_tenantid = None + + if app_type == "UserAssignedMsi": + raise Exception("UserAssignedMsi APP_TYPE is not supported") + + if app_type == "SingleTenant": + app_tenantid = ( + configuration.APP_TENANTID + if hasattr(configuration, "APP_TENANTID") + else None + ) + + if not app_id: + raise Exception("Property 'APP_ID' is expected in configuration object") + if not app_password: + raise Exception( + "Property 'APP_PASSWORD' is expected in configuration object" + ) + if not app_tenantid: + raise Exception( + "Property 'APP_TENANTID' is expected in configuration object" + ) + + super().__init__(app_id, app_password, app_tenantid, logger=logger) diff --git a/libraries/botframework-connector/botframework/connector/auth/_built_in_bot_framework_authentication.py b/libraries/botframework-connector/botframework/connector/auth/_built_in_bot_framework_authentication.py index 25c5b0acd..8be3b200f 100644 --- a/libraries/botframework-connector/botframework/connector/auth/_built_in_bot_framework_authentication.py +++ b/libraries/botframework-connector/botframework/connector/auth/_built_in_bot_framework_authentication.py @@ -166,7 +166,7 @@ async def create_user_token_client( credentials = await self._credentials_factory.create_credentials( app_id, - audience=self._to_channel_from_bot_oauth_scope, + oauth_scope=self._to_channel_from_bot_oauth_scope, login_endpoint=self._login_endpoint, validate_authority=True, ) diff --git a/libraries/botframework-connector/botframework/connector/auth/_parameterized_bot_framework_authentication.py b/libraries/botframework-connector/botframework/connector/auth/_parameterized_bot_framework_authentication.py index 3d857eccb..1388094fe 100644 --- a/libraries/botframework-connector/botframework/connector/auth/_parameterized_bot_framework_authentication.py +++ b/libraries/botframework-connector/botframework/connector/auth/_parameterized_bot_framework_authentication.py @@ -155,7 +155,7 @@ async def create_user_token_client( credentials = await self._credentials_factory.create_credentials( app_id, - audience=self._to_channel_from_bot_oauth_scope, + oauth_scope=self._to_channel_from_bot_oauth_scope, login_endpoint=self._to_channel_from_bot_login_url, validate_authority=self._validate_authority, ) @@ -274,6 +274,11 @@ async def _skill_validation_authenticate_channel_token( ignore_expiration=False, ) + if self._auth_configuration.valid_token_issuers: + validation_params.issuer.append( + self._auth_configuration.valid_token_issuers + ) + # TODO: what should the openIdMetadataUrl be here? token_extractor = JwtTokenExtractor( validation_params, @@ -362,6 +367,11 @@ async def _emulator_validation_authenticate_emulator_token( ignore_expiration=False, ) + if self._auth_configuration.valid_token_issuers: + to_bot_from_emulator_validation_params.issuer.append( + self._auth_configuration.valid_token_issuers + ) + token_extractor = JwtTokenExtractor( to_bot_from_emulator_validation_params, metadata_url=self._to_bot_from_emulator_open_id_metadata_url, diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index db657e25f..31153be41 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -7,7 +7,7 @@ import requests from msrest.authentication import Authentication -from botframework.connector.auth import AuthenticationConstants +from .authentication_constants import AuthenticationConstants class AppCredentials(Authentication): diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_configuration.py b/libraries/botframework-connector/botframework/connector/auth/authentication_configuration.py index 59642d9ff..93314692d 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_configuration.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_configuration.py @@ -3,12 +3,39 @@ from typing import Awaitable, Callable, Dict, List +from .authentication_constants import AuthenticationConstants + class AuthenticationConfiguration: def __init__( self, required_endorsements: List[str] = None, claims_validator: Callable[[List[Dict]], Awaitable] = None, + valid_token_issuers: List[str] = None, + tenant_id: str = None, ): self.required_endorsements = required_endorsements or [] self.claims_validator = claims_validator + self.valid_token_issuers = valid_token_issuers or [] + + if tenant_id: + self.add_tenant_issuers(self, tenant_id) + + @staticmethod + def add_tenant_issuers(authentication_configuration, tenant_id: str): + authentication_configuration.valid_token_issuers.append( + AuthenticationConstants.VALID_TOKEN_ISSUER_URL_TEMPLATE_V1.format(tenant_id) + ) + authentication_configuration.valid_token_issuers.append( + AuthenticationConstants.VALID_TOKEN_ISSUER_URL_TEMPLATE_V2.format(tenant_id) + ) + authentication_configuration.valid_token_issuers.append( + AuthenticationConstants.VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V1.format( + tenant_id + ) + ) + authentication_configuration.valid_token_issuers.append( + AuthenticationConstants.VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V2.format( + tenant_id + ) + ) diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py index 6cda3226f..28e5b696b 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py @@ -60,6 +60,22 @@ class AuthenticationConstants(ABC): "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" ) + # The V1 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + VALID_TOKEN_ISSUER_URL_TEMPLATE_V1 = "https://sts.windows.net/{0}/" + + # The V2 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + VALID_TOKEN_ISSUER_URL_TEMPLATE_V2 = "https://login.microsoftonline.com/{0}/v2.0" + + # The Government V1 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V1 = ( + "https://login.microsoftonline.us/{0}/" + ) + + # The Government V2 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V2 = ( + "https://login.microsoftonline.us/{0}/v2.0" + ) + # Allowed token signing algorithms. Tokens come from channels to the bot. The code # that uses this also supports tokens coming from the emulator. ALLOWED_SIGNING_ALGORITHMS = ["RS256", "RS384", "RS512"] diff --git a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py index e4cbddd39..e883d3db7 100644 --- a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py @@ -115,7 +115,7 @@ async def get_claims() -> ClaimsIdentity: ) is_gov = ( isinstance(channel_service_or_provider, ChannelProvider) - and channel_service_or_provider.is_public_azure() + and channel_service_or_provider.is_government() or isinstance(channel_service_or_provider, str) and JwtTokenValidation.is_government(channel_service_or_provider) ) diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py index d625d6ede..433c198af 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -14,9 +14,6 @@ class MicrosoftAppCredentials(AppCredentials, ABC): AppCredentials implementation using application ID and password. """ - MICROSOFT_APP_ID = "MicrosoftAppId" - MICROSOFT_PASSWORD = "MicrosoftPassword" - def __init__( self, app_id: str, diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py index eb59fe941..3c8e4b652 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py @@ -1,7 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from botframework.connector.auth import MicrosoftAppCredentials, GovernmentConstants +from .microsoft_app_credentials import MicrosoftAppCredentials +from .government_constants import GovernmentConstants class MicrosoftGovernmentAppCredentials(MicrosoftAppCredentials): @@ -16,12 +17,16 @@ def __init__( channel_auth_tenant: str = None, scope: str = None, ): - super().__init__(app_id, app_password, channel_auth_tenant, scope) - self.oauth_endpoint = GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL - self.oauth_scope = ( - scope if scope else GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + super().__init__( + app_id, + app_password, + channel_auth_tenant, + scope or GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, ) + # this sets super.oauth_endpoint value + self.oauth_endpoint = GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL + @staticmethod def empty(): return MicrosoftGovernmentAppCredentials("", "") diff --git a/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py b/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py index 1e14b496c..4dd44e79b 100644 --- a/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py +++ b/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py @@ -8,15 +8,22 @@ from .authentication_constants import AuthenticationConstants from .government_constants import GovernmentConstants from .microsoft_app_credentials import MicrosoftAppCredentials +from .microsoft_government_app_credentials import MicrosoftGovernmentAppCredentials from .service_client_credentials_factory import ServiceClientCredentialsFactory class PasswordServiceClientCredentialFactory(ServiceClientCredentialsFactory): def __init__( - self, app_id: str = None, password: str = None, *, logger: Logger = None + self, + app_id: str = None, + password: str = None, + tenant_id: str = None, + *, + logger: Logger = None ) -> None: self.app_id = app_id self.password = password + self.tenant_id = tenant_id self._logger = logger async def is_valid_app_id(self, app_id: str) -> bool: @@ -26,7 +33,11 @@ async def is_authentication_disabled(self) -> bool: return not self.app_id async def create_credentials( - self, app_id: str, audience: str, login_endpoint: str, validate_authority: bool + self, + app_id: str, + oauth_scope: str, + login_endpoint: str, + validate_authority: bool, ) -> Authentication: if await self.is_authentication_disabled(): return MicrosoftAppCredentials.empty() @@ -40,38 +51,25 @@ async def create_credentials( if normalized_endpoint.startswith( AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX ): - # TODO: Unpack necessity of these empty credentials based on the - # loginEndpoint as no tokensare fetched when auth is disabled. - credentials = ( - MicrosoftAppCredentials.empty() - if not app_id - else MicrosoftAppCredentials(app_id, self.password, None, audience) + credentials = MicrosoftAppCredentials( + app_id, self.password, self.tenant_id, oauth_scope ) + elif normalized_endpoint == GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL: - credentials = ( - MicrosoftAppCredentials( - None, - None, - None, - GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, - ) - if not app_id - else MicrosoftAppCredentials(app_id, self.password, None, audience) + credentials = MicrosoftGovernmentAppCredentials( + app_id, + self.password, + self.tenant_id, + oauth_scope, ) - normalized_endpoint = login_endpoint else: - credentials = ( - _PrivateCloudAppCredentials( - None, None, None, normalized_endpoint, validate_authority - ) - if not app_id - else MicrosoftAppCredentials( - app_id, - self.password, - audience, - normalized_endpoint, - validate_authority, - ) + credentials = _PrivateCloudAppCredentials( + app_id, + self.password, + self.tenant_id, + oauth_scope, + login_endpoint, + validate_authority, ) return credentials @@ -82,12 +80,13 @@ def __init__( self, app_id: str, password: str, + tenant_id: str, oauth_scope: str, oauth_endpoint: str, validate_authority: bool, ): super().__init__( - app_id, password, channel_auth_tenant=None, oauth_scope=oauth_scope + app_id, password, channel_auth_tenant=tenant_id, oauth_scope=oauth_scope ) self.oauth_endpoint = oauth_endpoint diff --git a/libraries/botframework-connector/botframework/connector/auth/service_client_credentials_factory.py b/libraries/botframework-connector/botframework/connector/auth/service_client_credentials_factory.py index 1c765ad9a..cbd008beb 100644 --- a/libraries/botframework-connector/botframework/connector/auth/service_client_credentials_factory.py +++ b/libraries/botframework-connector/botframework/connector/auth/service_client_credentials_factory.py @@ -28,7 +28,11 @@ async def is_authentication_disabled(self) -> bool: @abstractmethod async def create_credentials( - self, app_id: str, audience: str, login_endpoint: str, validate_authority: bool + self, + app_id: str, + oauth_scope: str, + login_endpoint: str, + validate_authority: bool, ) -> AppCredentials: """ A factory method for creating AppCredentials. diff --git a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py index d23572e3f..f7f08d33b 100644 --- a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py @@ -24,22 +24,6 @@ class SkillValidation: Validates JWT tokens sent to and from a Skill. """ - _token_validation_parameters = VerifyOptions( - issuer=[ - "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", # Auth v3.1, 1.0 token - "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", # Auth v3.1, 2.0 token - "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth v3.2, 1.0 token - "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth v3.2, 2.0 token - "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", # Auth for US Gov, 1.0 token - "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", # Auth for US Gov, 2.0 token - "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth for US Gov, 1.0 token - "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth for US Gov, 2.0 token - ], - audience=None, - clock_tolerance=timedelta(minutes=5), - ignore_expiration=False, - ) - @staticmethod def is_skill_token(auth_header: str) -> bool: """ @@ -119,8 +103,29 @@ async def authenticate_channel_token( else AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL ) + token_validation_parameters = VerifyOptions( + issuer=[ + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", # Auth v3.1, 1.0 token + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", # Auth v3.1, 2.0 token + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth v3.2, 1.0 token + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth v3.2, 2.0 token + "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", # Auth for US Gov, 1.0 token + "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", # Auth for US Gov, 2.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth for US Gov, 1.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth for US Gov, 2.0 token + ], + audience=None, + clock_tolerance=timedelta(minutes=5), + ignore_expiration=False, + ) + + if auth_configuration.valid_token_issuers: + token_validation_parameters.issuer.append( + auth_configuration.valid_token_issuers + ) + token_extractor = JwtTokenExtractor( - SkillValidation._token_validation_parameters, + token_validation_parameters, open_id_metadata_url, AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS, ) From d53c1ab84a1c662382fedd27da8c924059aaf1c4 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 6 Dec 2023 16:51:31 -0600 Subject: [PATCH 02/11] Pylint corrections --- .../connector/auth/authentication_constants.py | 12 ++++++++---- .../connector/auth/skill_validation.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py index 28e5b696b..ef0d1870f 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py @@ -60,18 +60,22 @@ class AuthenticationConstants(ABC): "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" ) - # The V1 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + # The V1 Azure AD token issuer URL template that will contain the tenant id where + # the token was issued from. VALID_TOKEN_ISSUER_URL_TEMPLATE_V1 = "https://sts.windows.net/{0}/" - # The V2 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + # The V2 Azure AD token issuer URL template that will contain the tenant id where + # the token was issued from. VALID_TOKEN_ISSUER_URL_TEMPLATE_V2 = "https://login.microsoftonline.com/{0}/v2.0" - # The Government V1 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + # The Government V1 Azure AD token issuer URL template that will contain the tenant id + # where the token was issued from. VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V1 = ( "https://login.microsoftonline.us/{0}/" ) - # The Government V2 Azure AD token issuer URL template that will contain the tenant id where the token was issued from. + # The Government V2 Azure AD token issuer URL template that will contain the tenant id + # where the token was issued from. VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V2 = ( "https://login.microsoftonline.us/{0}/v2.0" ) diff --git a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py index f7f08d33b..cc6cd41ab 100644 --- a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py @@ -105,14 +105,14 @@ async def authenticate_channel_token( token_validation_parameters = VerifyOptions( issuer=[ - "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", # Auth v3.1, 1.0 token - "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", # Auth v3.1, 2.0 token - "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth v3.2, 1.0 token - "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth v3.2, 2.0 token - "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", # Auth for US Gov, 1.0 token - "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", # Auth for US Gov, 2.0 token - "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # Auth for US Gov, 1.0 token - "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # Auth for US Gov, 2.0 token + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", # v3.1, 1.0 token + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", # v3.1, 2.0 token + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # v3.2, 1.0 token + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # v3.2, 2.0 token + "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", # US Gov, 1.0 token + "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", # US Gov, 2.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", # US Gov, 1.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", # US Gov, 2.0 token ], audience=None, clock_tolerance=timedelta(minutes=5), From 271372af10dcd0361739e3e1009ccfae5701b2b7 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 7 Dec 2023 08:46:43 -0600 Subject: [PATCH 03/11] Black correction --- .../botframework/connector/auth/authentication_constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py index ef0d1870f..ef0c1bf87 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py @@ -60,11 +60,11 @@ class AuthenticationConstants(ABC): "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" ) - # The V1 Azure AD token issuer URL template that will contain the tenant id where + # The V1 Azure AD token issuer URL template that will contain the tenant id where # the token was issued from. VALID_TOKEN_ISSUER_URL_TEMPLATE_V1 = "https://sts.windows.net/{0}/" - # The V2 Azure AD token issuer URL template that will contain the tenant id where + # The V2 Azure AD token issuer URL template that will contain the tenant id where # the token was issued from. VALID_TOKEN_ISSUER_URL_TEMPLATE_V2 = "https://login.microsoftonline.com/{0}/v2.0" @@ -74,7 +74,7 @@ class AuthenticationConstants(ABC): "https://login.microsoftonline.us/{0}/" ) - # The Government V2 Azure AD token issuer URL template that will contain the tenant id + # The Government V2 Azure AD token issuer URL template that will contain the tenant id # where the token was issued from. VALID_GOVERNMENT_TOKEN_ISSUER_URL_TEMPLATE_V2 = ( "https://login.microsoftonline.us/{0}/v2.0" From 86797285e606372339a0b728be453e80649fbe50 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 13 Dec 2023 10:11:08 -0600 Subject: [PATCH 04/11] SingleTenant Gov correction --- .../botframework/connector/auth/government_constants.py | 2 ++ .../connector/auth/microsoft_government_app_credentials.py | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index c15c8e41e..bb080a09a 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -19,6 +19,8 @@ class GovernmentConstants(ABC): "https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us" ) + DEFAULT_CHANNEL_AUTH_TENANT = "MicrosoftServices.onmicrosoft.us" + """ TO CHANNEL FROM BOT: OAuth scope to request """ diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py index 3c8e4b652..633e3342c 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py @@ -20,13 +20,10 @@ def __init__( super().__init__( app_id, app_password, - channel_auth_tenant, + channel_auth_tenant or GovernmentConstants.DEFAULT_CHANNEL_AUTH_TENANT, scope or GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, ) - # this sets super.oauth_endpoint value - self.oauth_endpoint = GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL - @staticmethod def empty(): return MicrosoftGovernmentAppCredentials("", "") From 7de1a11f669fcc5857802387ff997bf156924283 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 21 Dec 2023 11:45:06 -0600 Subject: [PATCH 05/11] Ported Gov SingleTenant fixes from DotNet --- .../botbuilder/core/bot_framework_adapter.py | 13 ---- .../tests/test_bot_framework_adapter.py | 12 ---- .../aiohttp/bot_framework_http_adapter.py | 6 -- .../auth/_bot_framework_client_impl.py | 5 +- ...ment_cloud_bot_framework_authentication.py | 2 +- .../connector/auth/app_credentials.py | 63 +++++++++---------- .../connector/auth/government_constants.py | 7 +++ .../connector/auth/jwt_token_validation.py | 3 - .../auth/microsoft_app_credentials.py | 8 --- .../microsoft_government_app_credentials.py | 13 +++- ...sword_service_client_credential_factory.py | 7 ++- .../tests/requirements.txt | 2 +- .../botframework-connector/tests/test_auth.py | 47 -------------- 13 files changed, 55 insertions(+), 133 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py index 41ce8ff72..42df5d73a 100644 --- a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py @@ -279,19 +279,6 @@ async def continue_conversation( context.turn_state[BotAdapter.BOT_CALLBACK_HANDLER_KEY] = callback context.turn_state[BotAdapter.BOT_OAUTH_SCOPE_KEY] = audience - # If we receive a valid app id in the incoming token claims, add the channel service URL to the - # trusted services list so we can send messages back. - # The service URL for skills is trusted because it is applied by the SkillHandler based on the original - # request received by the root bot - app_id_from_claims = JwtTokenValidation.get_app_id_from_claims( - claims_identity.claims - ) - if app_id_from_claims: - if SkillValidation.is_skill_claim( - claims_identity.claims - ) or await self._credential_provider.is_valid_appid(app_id_from_claims): - AppCredentials.trust_service_url(reference.service_url) - client = await self.create_connector_client( reference.service_url, claims_identity, audience ) diff --git a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py index 8e987665a..7e9268ee7 100644 --- a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py +++ b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py @@ -621,14 +621,8 @@ async def callback(context: TurnContext): scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY] assert AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE == scope - # Ensure the serviceUrl was added to the trusted hosts - assert AppCredentials.is_trusted_service(channel_service_url) - refs = ConversationReference(service_url=channel_service_url) - # Ensure the serviceUrl is NOT in the trusted hosts - assert not AppCredentials.is_trusted_service(channel_service_url) - await adapter.continue_conversation( refs, callback, claims_identity=skills_identity ) @@ -694,14 +688,8 @@ async def callback(context: TurnContext): scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY] assert skill_2_app_id == scope - # Ensure the serviceUrl was added to the trusted hosts - assert AppCredentials.is_trusted_service(skill_2_service_url) - refs = ConversationReference(service_url=skill_2_service_url) - # Ensure the serviceUrl is NOT in the trusted hosts - assert not AppCredentials.is_trusted_service(skill_2_service_url) - await adapter.continue_conversation( refs, callback, claims_identity=skills_identity, audience=skill_2_app_id ) diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_adapter.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_adapter.py index da1b7c3c3..879bdeacd 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_adapter.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_adapter.py @@ -188,12 +188,6 @@ async def _http_authenticate_request(self, request: Request) -> bool: ) ) - # Add ServiceURL to the cache of trusted sites in order to allow token refreshing. - self._credentials.trust_service_url( - claims_identity.claims.get( - AuthenticationConstants.SERVICE_URL_CLAIM - ) - ) self.claims_identity = claims_identity return True except Exception as error: diff --git a/libraries/botframework-connector/botframework/connector/auth/_bot_framework_client_impl.py b/libraries/botframework-connector/botframework/connector/auth/_bot_framework_client_impl.py index 512207cd4..df4313c0e 100644 --- a/libraries/botframework-connector/botframework/connector/auth/_bot_framework_client_impl.py +++ b/libraries/botframework-connector/botframework/connector/auth/_bot_framework_client_impl.py @@ -48,10 +48,6 @@ async def post_activity( conversation_id: str, activity: Activity, ) -> InvokeResponse: - if not from_bot_id: - raise TypeError("from_bot_id") - if not to_bot_id: - raise TypeError("to_bot_id") if not to_url: raise TypeError("to_url") if not service_url: @@ -100,6 +96,7 @@ async def post_activity( headers_dict = { "Content-type": "application/json; charset=utf-8", + "x-ms-conversation-id": conversation_id, } if token: headers_dict.update( diff --git a/libraries/botframework-connector/botframework/connector/auth/_government_cloud_bot_framework_authentication.py b/libraries/botframework-connector/botframework/connector/auth/_government_cloud_bot_framework_authentication.py index cbdaa61dc..8cde743e5 100644 --- a/libraries/botframework-connector/botframework/connector/auth/_government_cloud_bot_framework_authentication.py +++ b/libraries/botframework-connector/botframework/connector/auth/_government_cloud_bot_framework_authentication.py @@ -24,7 +24,7 @@ def __init__( ): super(_GovernmentCloudBotFrameworkAuthentication, self).__init__( GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, - GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL, + GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX, CallerIdConstants.us_gov_channel, GovernmentConstants.CHANNEL_SERVICE, GovernmentConstants.OAUTH_URL_GOV, diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index 31153be41..a27279d14 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -17,16 +17,8 @@ class AppCredentials(Authentication): """ schema = "Bearer" - - trustedHostNames = { - # "state.botframework.com": datetime.max, - # "state.botframework.azure.us": datetime.max, - "api.botframework.com": datetime.max, - "token.botframework.com": datetime.max, - "api.botframework.azure.us": datetime.max, - "token.botframework.azure.us": datetime.max, - } cache = {} + __tenant = None def __init__( self, @@ -38,50 +30,55 @@ def __init__( Initializes a new instance of MicrosoftAppCredentials class :param channel_auth_tenant: Optional. The oauth token tenant. """ - tenant = ( - channel_auth_tenant - if channel_auth_tenant - else AuthenticationConstants.DEFAULT_CHANNEL_AUTH_TENANT - ) + self.microsoft_app_id = app_id + self.tenant = channel_auth_tenant self.oauth_endpoint = ( - AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + tenant - ) - self.oauth_scope = ( - oauth_scope or AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + self._get_to_channel_from_bot_loginurl_prefix() + self.tenant ) + self.oauth_scope = oauth_scope or self._get_to_channel_from_bot_oauthscope() - self.microsoft_app_id = app_id + def _get_default_channelauth_tenant(self) -> str: + return AuthenticationConstants.DEFAULT_CHANNEL_AUTH_TENANT + + def _get_to_channel_from_bot_loginurl_prefix(self) -> str: + return AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + + def _get_to_channel_from_bot_oauthscope(self) -> str: + return AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + + @property + def tenant(self) -> str: + return self.__tenant + + @tenant.setter + def tenant(self, value: str): + self.__tenant = value or self._get_default_channelauth_tenant() @staticmethod def trust_service_url(service_url: str, expiration=None): """ + Obsolete: trust_service_url is not a required part of the security model. Checks if the service url is for a trusted host or not. :param service_url: The service url. :param expiration: The expiration time after which this service url is not trusted anymore. - :returns: True if the host of the service url is trusted; False otherwise. """ - if expiration is None: - expiration = datetime.now() + timedelta(days=1) - host = urlparse(service_url).hostname - if host is not None: - AppCredentials.trustedHostNames[host] = expiration @staticmethod def is_trusted_service(service_url: str) -> bool: """ + Obsolete: is_trusted_service is not a required part of the security model. Checks if the service url is for a trusted host or not. :param service_url: The service url. :returns: True if the host of the service url is trusted; False otherwise. """ - host = urlparse(service_url).hostname - if host is not None: - return AppCredentials._is_trusted_url(host) - return False + return True @staticmethod def _is_trusted_url(host: str) -> bool: - expiration = AppCredentials.trustedHostNames.get(host, datetime.min) - return expiration > (datetime.now() - timedelta(minutes=5)) + """ + Obsolete: _is_trusted_url is not a required part of the security model. + """ + return True # pylint: disable=arguments-differ def signed_session(self, session: requests.Session = None) -> requests.Session: @@ -92,7 +89,7 @@ def signed_session(self, session: requests.Session = None) -> requests.Session: if not session: session = requests.Session() - if not self._should_authorize(session): + if not self._should_set_token(session): session.headers.pop("Authorization", None) else: auth_token = self.get_access_token() @@ -101,7 +98,7 @@ def signed_session(self, session: requests.Session = None) -> requests.Session: return session - def _should_authorize( + def _should_set_token( self, session: requests.Session # pylint: disable=unused-argument ) -> bool: # We don't set the token if the AppId is not set, since it means that we are in an un-authenticated scenario. diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index bb080a09a..efcb2e4e3 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -14,11 +14,18 @@ class GovernmentConstants(ABC): """ TO CHANNEL FROM BOT: Login URL + + DEPRECATED: DO NOT USE """ TO_CHANNEL_FROM_BOT_LOGIN_URL = ( "https://login.microsoftonline.us/MicrosoftServices.onmicrosoft.us" ) + """ + TO CHANNEL FROM BOT: Login URL prefix + """ + TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX = "https://login.microsoftonline.com/" + DEFAULT_CHANNEL_AUTH_TENANT = "MicrosoftServices.onmicrosoft.us" """ diff --git a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py index e883d3db7..5638d8f31 100644 --- a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py @@ -76,9 +76,6 @@ async def authenticate_request( auth_configuration, ) - # On the standard Auth path, we need to trust the URL that was incoming. - MicrosoftAppCredentials.trust_service_url(activity.service_url) - return claims_identity @staticmethod diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py index 433c198af..1ba383f80 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -70,11 +70,3 @@ def __get_msal_app(self): ) return self.app - - def _should_authorize(self, session: requests.Session) -> bool: - """ - Override of AppCredentials._should_authorize - :param session: - :return: - """ - return self.microsoft_app_id and self.microsoft_app_password diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py index 633e3342c..a2d9a6f1e 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_government_app_credentials.py @@ -20,10 +20,19 @@ def __init__( super().__init__( app_id, app_password, - channel_auth_tenant or GovernmentConstants.DEFAULT_CHANNEL_AUTH_TENANT, - scope or GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, + channel_auth_tenant, + scope, ) @staticmethod def empty(): return MicrosoftGovernmentAppCredentials("", "") + + def _get_default_channelauth_tenant(self) -> str: + return GovernmentConstants.DEFAULT_CHANNEL_AUTH_TENANT + + def _get_to_channel_from_bot_loginurl_prefix(self) -> str: + return GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + + def _get_to_channel_from_bot_oauthscope(self) -> str: + return GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE diff --git a/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py b/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py index 4dd44e79b..a8ff069d2 100644 --- a/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py +++ b/libraries/botframework-connector/botframework/connector/auth/password_service_client_credential_factory.py @@ -45,7 +45,7 @@ async def create_credentials( if not await self.is_valid_app_id(app_id): raise Exception("Invalid app_id") - credentials: MicrosoftAppCredentials = None + credentials: MicrosoftAppCredentials normalized_endpoint = login_endpoint.lower() if login_endpoint else "" if normalized_endpoint.startswith( @@ -54,8 +54,9 @@ async def create_credentials( credentials = MicrosoftAppCredentials( app_id, self.password, self.tenant_id, oauth_scope ) - - elif normalized_endpoint == GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL: + elif normalized_endpoint.startswith( + GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + ): credentials = MicrosoftGovernmentAppCredentials( app_id, self.password, diff --git a/libraries/botframework-connector/tests/requirements.txt b/libraries/botframework-connector/tests/requirements.txt index 62eb5aba5..dfbb418bd 100644 --- a/libraries/botframework-connector/tests/requirements.txt +++ b/libraries/botframework-connector/tests/requirements.txt @@ -1,5 +1,5 @@ pytest-cov>=2.6.0 -pytest~=6.2.3 +pytest~=7.3.1 pyyaml==6.0 pytest-asyncio==0.15.1 ddt==1.2.1 \ No newline at end of file diff --git a/libraries/botframework-connector/tests/test_auth.py b/libraries/botframework-connector/tests/test_auth.py index 39a29a1ea..0c10b0e94 100644 --- a/libraries/botframework-connector/tests/test_auth.py +++ b/libraries/botframework-connector/tests/test_auth.py @@ -352,53 +352,6 @@ async def test_channel_authentication_disabled_should_be_anonymous(self): == AuthenticationConstants.ANONYMOUS_AUTH_TYPE ) - @pytest.mark.asyncio - # Tests with no authentication header and makes sure the service URL is not added to the trusted list. - async def test_channel_authentication_disabled_service_url_should_not_be_trusted( - self, - ): - activity = Activity(service_url="https://webchat.botframework.com/") - header = "" - credentials = SimpleCredentialProvider("", "") - - await JwtTokenValidation.authenticate_request(activity, header, credentials) - - assert not MicrosoftAppCredentials.is_trusted_service( - "https://webchat.botframework.com/" - ) - - # @pytest.mark.asyncio - # async def test_emulator_auth_header_correct_app_id_and_service_url_with_gov_channel_service_should_validate( - # self, - # ): - # await jwt_token_validation_validate_auth_header_with_channel_service_succeeds( - # "", # emulator creds - # "", - # GovernmentConstants.CHANNEL_SERVICE, - # ) - # - # await jwt_token_validation_validate_auth_header_with_channel_service_succeeds( - # "", # emulator creds - # "", - # SimpleChannelProvider(GovernmentConstants.CHANNEL_SERVICE), - # ) - - # @pytest.mark.asyncio - # async def - # test_emulator_auth_header_correct_app_id_and_service_url_with_private_channel_service_should_validate( - # self, - # ): - # await jwt_token_validation_validate_auth_header_with_channel_service_succeeds( - # "", # emulator creds - # "", - # "TheChannel", - # ) - # - # await jwt_token_validation_validate_auth_header_with_channel_service_succeeds( - # "", # emulator creds - # "", - # SimpleChannelProvider("TheChannel"), - # ) @pytest.mark.asyncio async def test_government_channel_validation_succeeds(self): From 1e1a76f34b2edf22195ea1cdc65d384d9dded275 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 21 Dec 2023 11:59:31 -0600 Subject: [PATCH 06/11] Black correction --- libraries/botframework-connector/tests/test_auth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/botframework-connector/tests/test_auth.py b/libraries/botframework-connector/tests/test_auth.py index 0c10b0e94..cc3abf66a 100644 --- a/libraries/botframework-connector/tests/test_auth.py +++ b/libraries/botframework-connector/tests/test_auth.py @@ -352,7 +352,6 @@ async def test_channel_authentication_disabled_should_be_anonymous(self): == AuthenticationConstants.ANONYMOUS_AUTH_TYPE ) - @pytest.mark.asyncio async def test_government_channel_validation_succeeds(self): credentials = SimpleCredentialProvider("", "") From d427e3019bf110158fd7fea70e42e3866696c097 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 21 Dec 2023 12:20:28 -0600 Subject: [PATCH 07/11] Pylint corrections --- .../botframework/connector/auth/app_credentials.py | 7 ++----- .../botframework/connector/auth/jwt_token_validation.py | 1 - .../connector/auth/microsoft_app_credentials.py | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index a27279d14..a7ac7655d 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -1,9 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from datetime import datetime, timedelta -from urllib.parse import urlparse - import requests from msrest.authentication import Authentication @@ -64,7 +61,7 @@ def trust_service_url(service_url: str, expiration=None): """ @staticmethod - def is_trusted_service(service_url: str) -> bool: + def is_trusted_service(service_url: str) -> bool: # pylint: disable=unused-argument """ Obsolete: is_trusted_service is not a required part of the security model. Checks if the service url is for a trusted host or not. @@ -74,7 +71,7 @@ def is_trusted_service(service_url: str) -> bool: return True @staticmethod - def _is_trusted_url(host: str) -> bool: + def _is_trusted_url(host: str) -> bool: # pylint: disable=unused-argument """ Obsolete: _is_trusted_url is not a required part of the security model. """ diff --git a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py index 5638d8f31..659559f14 100644 --- a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py @@ -11,7 +11,6 @@ from .emulator_validation import EmulatorValidation from .enterprise_channel_validation import EnterpriseChannelValidation from .channel_validation import ChannelValidation -from .microsoft_app_credentials import MicrosoftAppCredentials from .credential_provider import CredentialProvider from .claims_identity import ClaimsIdentity from .government_constants import GovernmentConstants diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py index 1ba383f80..24c230007 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -3,7 +3,6 @@ from abc import ABC -import requests from msal import ConfidentialClientApplication from .app_credentials import AppCredentials From 0fc55b87900fd96be4ba622c965412b64cf86cbe Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 21 Dec 2023 12:27:25 -0600 Subject: [PATCH 08/11] Black corrections for app_credentials --- .../botframework/connector/auth/app_credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index a7ac7655d..4440d9cff 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -61,7 +61,7 @@ def trust_service_url(service_url: str, expiration=None): """ @staticmethod - def is_trusted_service(service_url: str) -> bool: # pylint: disable=unused-argument + def is_trusted_service(service_url: str) -> bool: # pylint: disable=unused-argument """ Obsolete: is_trusted_service is not a required part of the security model. Checks if the service url is for a trusted host or not. @@ -71,7 +71,7 @@ def is_trusted_service(service_url: str) -> bool: # pylint: disable=unused-argum return True @staticmethod - def _is_trusted_url(host: str) -> bool: # pylint: disable=unused-argument + def _is_trusted_url(host: str) -> bool: # pylint: disable=unused-argument """ Obsolete: _is_trusted_url is not a required part of the security model. """ From 18b436923cf36f10ee57b344cfb9a761ad2fafa6 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Thu, 21 Dec 2023 16:20:47 -0600 Subject: [PATCH 09/11] Corrected AppCredentials._should_set_token --- .../botframework/connector/auth/app_credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index 4440d9cff..ef1436397 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -101,7 +101,7 @@ def _should_set_token( # We don't set the token if the AppId is not set, since it means that we are in an un-authenticated scenario. return ( self.microsoft_app_id != AuthenticationConstants.ANONYMOUS_SKILL_APP_ID - and self.microsoft_app_id is not None + and self.microsoft_app_id ) def get_access_token(self, force_refresh: bool = False) -> str: From 898c00c048894610689f90796872f950fd7382e0 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 7 Feb 2024 09:47:31 -0600 Subject: [PATCH 10/11] Changed auth constant to match setting name --- .../botframework/connector/auth/authentication_constants.py | 4 ++-- .../botframework/connector/auth/channel_validation.py | 2 +- .../botframework/connector/auth/emulator_validation.py | 4 ++-- .../connector/auth/government_channel_validation.py | 2 +- .../botframework/connector/auth/government_constants.py | 4 ++-- .../botframework/connector/auth/skill_validation.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py index ef0c1bf87..f1a24de08 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py @@ -45,7 +45,7 @@ class AuthenticationConstants(ABC): EMULATE_OAUTH_CARDS_KEY = "EmulateOAuthCards" # TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA - TO_BOT_FROM_CHANNEL_OPEN_ID_METADATA_URL = ( + TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL = ( "https://login.botframework.com/v1/.well-known/openidconfiguration" ) @@ -56,7 +56,7 @@ class AuthenticationConstants(ABC): ) # TO BOT FROM EMULATOR: OpenID metadata document for tokens coming from MSA - TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL = ( + TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL = ( "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" ) diff --git a/libraries/botframework-connector/botframework/connector/auth/channel_validation.py b/libraries/botframework-connector/botframework/connector/auth/channel_validation.py index 671086a80..590e39862 100644 --- a/libraries/botframework-connector/botframework/connector/auth/channel_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/channel_validation.py @@ -88,7 +88,7 @@ async def authenticate_channel_token( metadata_endpoint = ( ChannelValidation.open_id_metadata_endpoint if ChannelValidation.open_id_metadata_endpoint - else AuthenticationConstants.TO_BOT_FROM_CHANNEL_OPEN_ID_METADATA_URL + else AuthenticationConstants.TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL ) token_extractor = JwtTokenExtractor( diff --git a/libraries/botframework-connector/botframework/connector/auth/emulator_validation.py b/libraries/botframework-connector/botframework/connector/auth/emulator_validation.py index 57c961ddc..4cd43ea9e 100644 --- a/libraries/botframework-connector/botframework/connector/auth/emulator_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/emulator_validation.py @@ -113,9 +113,9 @@ async def authenticate_emulator_token( is_gov = JwtTokenValidation.is_government(channel_service_or_provider) open_id_metadata = ( - GovernmentConstants.TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL + GovernmentConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL if is_gov - else AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL + else AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL ) token_extractor = JwtTokenExtractor( diff --git a/libraries/botframework-connector/botframework/connector/auth/government_channel_validation.py b/libraries/botframework-connector/botframework/connector/auth/government_channel_validation.py index c7438865e..d3ec16da1 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_channel_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_channel_validation.py @@ -33,7 +33,7 @@ async def authenticate_channel_token( endpoint = ( GovernmentChannelValidation.OPEN_ID_METADATA_ENDPOINT if GovernmentChannelValidation.OPEN_ID_METADATA_ENDPOINT - else GovernmentConstants.TO_BOT_FROM_CHANNEL_OPEN_ID_METADATA_URL + else GovernmentConstants.TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL ) token_extractor = JwtTokenExtractor( GovernmentChannelValidation.TO_BOT_FROM_GOVERNMENT_CHANNEL_TOKEN_VALIDATION_PARAMETERS, diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index efcb2e4e3..6574859eb 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -46,14 +46,14 @@ class GovernmentConstants(ABC): """ TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA """ - TO_BOT_FROM_CHANNEL_OPEN_ID_METADATA_URL = ( + TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL = ( "https://login.botframework.azure.us/v1/.well-known/openidconfiguration" ) """ TO BOT FROM GOV EMULATOR: OpenID metadata document for tokens coming from MSA """ - TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL = ( + TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL = ( "https://login.microsoftonline.us/" "cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0/" ".well-known/openid-configuration" diff --git a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py index cc6cd41ab..b708e27cb 100644 --- a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py @@ -98,9 +98,9 @@ async def authenticate_channel_token( is_gov = JwtTokenValidation.is_government(channel_service_or_provider) open_id_metadata_url = ( - GovernmentConstants.TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL + GovernmentConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL if is_gov - else AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPEN_ID_METADATA_URL + else AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL ) token_validation_parameters = VerifyOptions( From eb4ab0b1e706c57340187f8244040b5598eb4fae Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 13 Feb 2024 09:51:05 -0600 Subject: [PATCH 11/11] black corrections --- libraries/botbuilder-adapters-slack/tests/test_slack_client.py | 2 +- .../botframework/connector/auth/app_credentials.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/tests/test_slack_client.py b/libraries/botbuilder-adapters-slack/tests/test_slack_client.py index 9174058a5..1f13c19b0 100644 --- a/libraries/botbuilder-adapters-slack/tests/test_slack_client.py +++ b/libraries/botbuilder-adapters-slack/tests/test_slack_client.py @@ -12,7 +12,7 @@ import requests import pytest -SKIP = os.getenv("SlackChannel") == '' +SKIP = os.getenv("SlackChannel") == "" class SlackClient(aiounittest.AsyncTestCase): diff --git a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py index ef1436397..b054f0c2f 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -101,7 +101,7 @@ def _should_set_token( # We don't set the token if the AppId is not set, since it means that we are in an un-authenticated scenario. return ( self.microsoft_app_id != AuthenticationConstants.ANONYMOUS_SKILL_APP_ID - and self.microsoft_app_id + and self.microsoft_app_id ) def get_access_token(self, force_refresh: bool = False) -> str: