From 0c385b99dbe85d458b4e3d40a14d43506f93283b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:31:57 -0600 Subject: [PATCH 001/102] Bump aiohttp in /libraries/botbuilder-integration-aiohttp (#2041) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.6 to 3.9.0. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.6...v3.9.0) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 8fa79efe2..2a614a9a6 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest==0.6.* botframework-connector==4.15.0 botbuilder-schema==4.15.0 -aiohttp==3.8.6 +aiohttp==3.9.0 From 3b0465cc2a55da475a4c33aad9b53195b9bc245e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:39:46 -0600 Subject: [PATCH 002/102] Bump aiohttp from 3.8.6 to 3.9.0 in /libraries/botbuilder-adapters-slack (#2042) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.6 to 3.9.0. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.6...v3.9.0) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 4e707ec41..d157c7b0e 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.8.6 +aiohttp==3.9.0 pyslack botbuilder-core==4.15.0 slackclient From 62bdf0ff6f4285d0c0779779f59a1c80d1694be8 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Wed, 29 Nov 2023 09:42:50 -0600 Subject: [PATCH 003/102] Pipeline updates --- pipelines/botbuilder-python-ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 04a848e1f..802f49481 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -6,8 +6,8 @@ variables: COVERALLS_GIT_COMMIT: $(Build.SourceVersion) COVERALLS_SERVICE_JOB_ID: $(Build.BuildId) COVERALLS_SERVICE_NAME: python-ci - python.37: 3.7.x - python.38: 3.8.x + python.37: 3.7 + python.38: 3.8 # python.311: 3.11.x # PythonCoverallsToken: get this from Azure @@ -60,6 +60,12 @@ jobs: pip install black==22.3.0 displayName: 'Install dependencies' + - script: 'black --check libraries' + displayName: 'Check Black compliant' + + - script: 'pylint --rcfile=.pylintrc libraries' + displayName: Pylint + - script: | pip install pytest pip install pytest-cov @@ -80,12 +86,6 @@ jobs: testResultsFiles: '**/test-results.$(PYTHON_VERSION).xml' testRunTitle: 'Python $(PYTHON_VERSION)' - - script: 'black --check libraries' - displayName: 'Check Black compliant' - - - script: 'pylint --rcfile=.pylintrc libraries' - displayName: Pylint - - script: 'COVERALLS_REPO_TOKEN=$(PythonCoverallsToken) coveralls' displayName: 'Push test results to coveralls https://coveralls.io/github/microsoft/botbuilder-python' continueOnError: true From 5982105b5aa402e98520f916d86710d3c3c5e087 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:51:17 -0600 Subject: [PATCH 004/102] Bump cryptography from 41.0.4 to 41.0.6 in /libraries/botbuilder-dialogs (#2044) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.4 to 41.0.6. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.4...41.0.6) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-dialogs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 373827330..06e095448 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botbuilder-core==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.4 +cryptography==41.0.6 aiounittest==1.3.0 From 84945805d8f140c3f41b9d9c2efa8059b8ebb3a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:09:30 -0600 Subject: [PATCH 005/102] Bump cryptography in /libraries/botframework-connector (#2045) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.4 to 41.0.6. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.4...41.0.6) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botframework-connector/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index f90cd0860..91cf9bd18 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -2,5 +2,5 @@ msrest==0.6.* botbuilder-schema==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.4 +cryptography==41.0.6 msal==1.* From 3de254694d05131d830c7e15a5bea6add55a6afa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:00:00 -0600 Subject: [PATCH 006/102] Bump cryptography from 41.0.4 to 41.0.6 in /libraries/botbuilder-core (#2046) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.4 to 41.0.6. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.4...41.0.6) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-core/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 00c320b22..0d293d6f9 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botframework-streaming==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.4 +cryptography==41.0.6 aiounittest==1.3.0 \ No newline at end of file From c5c276de97168a0b68b439023b2b8cdfee23d520 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 4 Dec 2023 19:50:18 -0600 Subject: [PATCH 007/102] msrest to 0.7.* (#2051) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-ai/requirements.txt | 2 +- libraries/botbuilder-applicationinsights/requirements.txt | 2 +- libraries/botbuilder-core/requirements.txt | 2 +- libraries/botbuilder-dialogs/requirements.txt | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- libraries/botbuilder-schema/requirements.txt | 2 +- libraries/botbuilder-schema/setup.py | 2 +- libraries/botframework-connector/requirements.txt | 2 +- libraries/botframework-connector/setup.py | 2 +- libraries/botframework-streaming/requirements.txt | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/botbuilder-ai/requirements.txt b/libraries/botbuilder-ai/requirements.txt index b1b9ce14a..221374746 100644 --- a/libraries/botbuilder-ai/requirements.txt +++ b/libraries/botbuilder-ai/requirements.txt @@ -1,4 +1,4 @@ -msrest==0.6.* +msrest== 0.7.* botbuilder-schema==4.15.0 botbuilder-core==4.15.0 requests==2.31.0 diff --git a/libraries/botbuilder-applicationinsights/requirements.txt b/libraries/botbuilder-applicationinsights/requirements.txt index 13d71acfd..7d22c3724 100644 --- a/libraries/botbuilder-applicationinsights/requirements.txt +++ b/libraries/botbuilder-applicationinsights/requirements.txt @@ -1,3 +1,3 @@ -msrest==0.6.* +msrest== 0.7.* botbuilder-core==4.15.0 aiounittest==1.3.0 \ No newline at end of file diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 0d293d6f9..5a8a126bb 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -1,4 +1,4 @@ -msrest==0.6.* +msrest== 0.7.* botframework-connector==4.15.0 botbuilder-schema==4.15.0 botframework-streaming==4.15.0 diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 06e095448..3d2fd9080 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -1,4 +1,4 @@ -msrest==0.6.* +msrest== 0.7.* botframework-connector==4.15.0 botbuilder-schema==4.15.0 botbuilder-core==4.15.0 diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 2a614a9a6..558ae91d3 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ -msrest==0.6.* +msrest== 0.7.* botframework-connector==4.15.0 botbuilder-schema==4.15.0 aiohttp==3.9.0 diff --git a/libraries/botbuilder-schema/requirements.txt b/libraries/botbuilder-schema/requirements.txt index 908ffb023..c6b07eaec 100644 --- a/libraries/botbuilder-schema/requirements.txt +++ b/libraries/botbuilder-schema/requirements.txt @@ -1,2 +1,2 @@ aiounittest==1.3.0 -msrest==0.6.* \ No newline at end of file +msrest== 0.7.* \ No newline at end of file diff --git a/libraries/botbuilder-schema/setup.py b/libraries/botbuilder-schema/setup.py index 3f056bc72..ac285ff01 100644 --- a/libraries/botbuilder-schema/setup.py +++ b/libraries/botbuilder-schema/setup.py @@ -6,7 +6,7 @@ NAME = "botbuilder-schema" VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" -REQUIRES = ["msrest==0.6.*", "urllib3<2.0.0"] +REQUIRES = ["msrest== 0.7.*", "urllib3<2.0.0"] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 91cf9bd18..0b30023a1 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -1,4 +1,4 @@ -msrest==0.6.* +msrest==0.7.* botbuilder-schema==4.15.0 requests==2.31.0 PyJWT==2.4.0 diff --git a/libraries/botframework-connector/setup.py b/libraries/botframework-connector/setup.py index 15411c492..2e6c8fe64 100644 --- a/libraries/botframework-connector/setup.py +++ b/libraries/botframework-connector/setup.py @@ -7,7 +7,7 @@ NAME = "botframework-connector" VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" REQUIRES = [ - "msrest==0.6.*", + "msrest==0.7.*", # "requests>=2.23.0,<2.26", "PyJWT>=2.4.0", "botbuilder-schema==4.15.0", diff --git a/libraries/botframework-streaming/requirements.txt b/libraries/botframework-streaming/requirements.txt index 004ce4c73..c806f7e77 100644 --- a/libraries/botframework-streaming/requirements.txt +++ b/libraries/botframework-streaming/requirements.txt @@ -1,3 +1,3 @@ -msrest==0.6.* +msrest==0.7.* botframework-connector>=4.15.0 botbuilder-schema>=4.15.0 \ No newline at end of file From e61d4b39ec5ddb59bcbe63588079e1cecc8e4766 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 5 Dec 2023 08:23:47 -0600 Subject: [PATCH 008/102] Dialogs: regex version bump (#2050) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-dialogs/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/setup.py b/libraries/botbuilder-dialogs/setup.py index 6e97715a5..574f8bbe7 100644 --- a/libraries/botbuilder-dialogs/setup.py +++ b/libraries/botbuilder-dialogs/setup.py @@ -5,7 +5,7 @@ from setuptools import setup REQUIRES = [ - "regex<=2019.08.19", + "regex>=2022.1.18", "emoji==1.7.0", "recognizers-text-date-time>=1.0.2a1", "recognizers-text-number-with-unit>=1.0.2a1", From 7b064bb9f916afc10e931f3713183f57e1d7ca47 Mon Sep 17 00:00:00 2001 From: Alex Acebo Date: Mon, 18 Dec 2023 13:45:34 -0800 Subject: [PATCH 009/102] fix usage of ResourceResponse to use named argument (#2057) --- libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py | 2 +- libraries/botbuilder-core/tests/simple_adapter.py | 2 +- .../tests/teams/simple_adapter_with_create_conversation.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py index c5eda9589..7e996c90c 100644 --- a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py +++ b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py @@ -100,7 +100,7 @@ async def send_activities( ) ) - response = response or ResourceResponse(activity.id or "") + response = response or ResourceResponse(id=activity.id or "") responses.append(response) diff --git a/libraries/botbuilder-core/tests/simple_adapter.py b/libraries/botbuilder-core/tests/simple_adapter.py index ae68dc323..2ba3f31b8 100644 --- a/libraries/botbuilder-core/tests/simple_adapter.py +++ b/libraries/botbuilder-core/tests/simple_adapter.py @@ -75,7 +75,7 @@ async def update_activity(self, context: TurnContext, activity: Activity): if self._call_on_update is not None: self._call_on_update(activity) - return ResourceResponse(activity.id) + return ResourceResponse(id=activity.id) async def process_request(self, activity, handler): context = TurnContext(self, activity) diff --git a/libraries/botbuilder-core/tests/teams/simple_adapter_with_create_conversation.py b/libraries/botbuilder-core/tests/teams/simple_adapter_with_create_conversation.py index d1801d978..cacfbd5ed 100644 --- a/libraries/botbuilder-core/tests/teams/simple_adapter_with_create_conversation.py +++ b/libraries/botbuilder-core/tests/teams/simple_adapter_with_create_conversation.py @@ -76,7 +76,7 @@ async def update_activity(self, context: TurnContext, activity: Activity): if self._call_on_update is not None: self._call_on_update(activity) - return ResourceResponse(activity.id) + return ResourceResponse(id=activity.id) async def process_request(self, activity, handler): context = TurnContext(self, activity) From e14f4f4e6a26b439872187afab6f322758730209 Mon Sep 17 00:00:00 2001 From: Alex Acebo Date: Tue, 16 Jan 2024 12:04:51 -0500 Subject: [PATCH 010/102] Python 3.11 Pylint (#2060) * update pipeline * upgrade rc file * Update .pylintrc --- .pylintrc | 908 ++++++++++-------- .../django_tests/tests.py | 4 +- old.pylintrc | 593 ++++++++++++ pipelines/botbuilder-python-ci.yml | 8 +- 4 files changed, 1103 insertions(+), 410 deletions(-) create mode 100644 old.pylintrc diff --git a/.pylintrc b/.pylintrc index 955005f07..70444a1d8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,24 +1,77 @@ [MASTER] +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) extension-pkg-whitelist= -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,build,botbuilder-schema,samples,django_tests,Generator,operations,operations_async,schema,tests +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns=setup.py,azure_bdist_wheel.py +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. jobs=1 # Control the amount of potential inferred values when inferring a single @@ -26,15 +79,25 @@ jobs=1 # complex, nested conditions. limit-inference-results=100 -# List of plugins (as comma separated values of python modules names) to load, +# List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes -# Specify a configuration file. -#rcfile= +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.8 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. @@ -44,340 +107,8 @@ suggestion-mode=yes # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - bad-continuation, - duplicate-code, - redefined-outer-name, - missing-docstring, - too-many-instance-attributes, - too-few-public-methods, - redefined-builtin, - too-many-arguments, - no-self-use, - fixme, - broad-except, - bare-except, - too-many-public-methods, - cyclic-import, - too-many-locals, - too-many-function-args, - too-many-return-statements, - import-error, - no-name-in-module, - too-many-branches, - too-many-ancestors, - too-many-nested-blocks, - attribute-defined-outside-init - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[LOGGING] - -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=120 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= [BASIC] @@ -386,13 +117,15 @@ min-similarity-lines=4 argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- -# naming-style. +# naming-style. If left empty, argument names will be checked with the set +# naming style. #argument-rgx= # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming # style. #attr-rgx= @@ -404,24 +137,38 @@ bad-names=foo, tutu, tata +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. #class-attribute-rgx= +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- -# style. +# style. If left empty, class names will be checked with the set naming style. #class-rgx= # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming # style. #const-rgx= @@ -433,7 +180,8 @@ docstring-min-length=-1 function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- -# naming-style. +# naming-style. If left empty, function names will be checked with the set +# naming style. #function-rgx= # Good variable names which should always be accepted, separated by a comma. @@ -444,6 +192,10 @@ good-names=i, Run, _ +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + # Include a hint for the correct naming format with invalid-name. include-naming-hint=no @@ -451,21 +203,22 @@ include-naming-hint=no inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. #inlinevar-rgx= # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- -# style. +# style. If left empty, method names will be checked with the set naming style. #method-rgx= # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- -# style. +# style. If left empty, module names will be checked with the set naming style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when @@ -481,86 +234,62 @@ no-docstring-rgx=^_ # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- -# naming-style. +# naming-style. If left empty, variable names will be checked with the set +# naming style. #variable-rgx= -[STRING] +[CLASSES] -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. -check-str-concat-over-line-jumps=no - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, - setUp + setUp, + __post_init__ # List of member names, which should be excluded from the protected access # warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls +valid-metaclass-classmethod-first-arg=mcs,cls [DESIGN] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + # Maximum number of arguments for function / method. max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 -# Maximum number of boolean expressions in an if statement. +# Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of branch for function / method body. @@ -587,7 +316,376 @@ min-public-methods=2 [EXCEPTIONS] -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + duplicate-code, + redefined-outer-name, + missing-docstring, + too-many-instance-attributes, + too-few-public-methods, + redefined-builtin, + too-many-arguments, + fixme, + broad-except, + bare-except, + too-many-public-methods, + cyclic-import, + too-many-locals, + too-many-function-args, + too-many-return-statements, + import-error, + no-name-in-module, + too-many-branches, + too-many-ancestors, + too-many-nested-blocks, + attribute-defined-outside-init, + super-with-arguments, + missing-timeout, + broad-exception-raised, + exec-used, + unspecified-encoding, + unused-variable, + consider-using-f-string, + raise-missing-from, + invalid-name, + useless-object-inheritance, + no-else-raise, + implicit-str-concat, + use-dict-literal, + use-list-literal, + unnecessary-dunder-call, + consider-using-in, + consider-using-with, + useless-parent-delegation, + f-string-without-interpolation, + global-variable-not-assigned, + dangerous-default-value, + wrong-import-order, + wrong-import-position, + ungrouped-imports, + import-outside-toplevel, + consider-using-from-import, + reimported, + unused-import, + unused-argument, + arguments-renamed, + unused-private-member, + unidiomatic-typecheck, + protected-access, + used-before-assignment, + invalid-overridden-method, + no-member, + deprecated-module, + too-many-lines, + c-extension-no-member, + unsubscriptable-object + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/libraries/botbuilder-applicationinsights/django_tests/tests.py b/libraries/botbuilder-applicationinsights/django_tests/tests.py index 180aa72b2..ba2fdc912 100644 --- a/libraries/botbuilder-applicationinsights/django_tests/tests.py +++ b/libraries/botbuilder-applicationinsights/django_tests/tests.py @@ -195,7 +195,9 @@ def test_logger(self): ) def test_thrower(self): - """Tests that unhandled exceptions generate an exception telemetry item parented to the request telemetry item""" + """ + Tests that unhandled exceptions generate an exception telemetry item parented to the request telemetry item + """ response = self.invoke_post("thrower") self.assertEqual(response.status_code, 500) diff --git a/old.pylintrc b/old.pylintrc new file mode 100644 index 000000000..955005f07 --- /dev/null +++ b/old.pylintrc @@ -0,0 +1,593 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,build,botbuilder-schema,samples,django_tests,Generator,operations,operations_async,schema,tests + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns=setup.py,azure_bdist_wheel.py + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + bad-continuation, + duplicate-code, + redefined-outer-name, + missing-docstring, + too-many-instance-attributes, + too-few-public-methods, + redefined-builtin, + too-many-arguments, + no-self-use, + fixme, + broad-except, + bare-except, + too-many-public-methods, + cyclic-import, + too-many-locals, + too-many-function-args, + too-many-return-statements, + import-error, + no-name-in-module, + too-many-branches, + too-many-ancestors, + too-many-nested-blocks, + attribute-defined-outside-init + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement. +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 802f49481..eecc05394 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -8,7 +8,7 @@ variables: COVERALLS_SERVICE_NAME: python-ci python.37: 3.7 python.38: 3.8 - # python.311: 3.11.x + python.311: 3.11 # PythonCoverallsToken: get this from Azure jobs: @@ -24,8 +24,8 @@ jobs: PYTHON_VERSION: '$(python.37)' Python38: PYTHON_VERSION: '$(python.38)' - # Python311: - # PYTHON_VERSION: '$(python.311)' + Python311: + PYTHON_VERSION: '$(python.311)' maxParallel: 3 steps: @@ -56,7 +56,7 @@ jobs: pip install -r ./libraries/botbuilder-core/tests/requirements.txt pip install -r ./libraries/botbuilder-ai/tests/requirements.txt pip install coveralls - pip install pylint==2.4.4 + pip install pylint==2.17 pip install black==22.3.0 displayName: 'Install dependencies' From 786650eb1224576021bbc886598bea1ae9d338e1 Mon Sep 17 00:00:00 2001 From: ali Date: Fri, 26 Jan 2024 15:58:43 +0100 Subject: [PATCH 011/102] save a reference to created tasks, to avoid tasks disappearing mid-execution (#2005) Co-authored-by: tracyboehrer --- .../integration/aiohttp/streaming/aiohttp_web_socket.py | 2 +- .../botframework/streaming/protocol_adapter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py index 334c637fb..2cd4ec13b 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py @@ -29,7 +29,7 @@ def __init__( def dispose(self): if self._session: - asyncio.create_task(self._session.close()) + task = asyncio.create_task(self._session.close()) async def close(self, close_status: WebSocketCloseStatus, status_description: str): await self._aiohttp_ws.close( diff --git a/libraries/botframework-streaming/botframework/streaming/protocol_adapter.py b/libraries/botframework-streaming/botframework/streaming/protocol_adapter.py index 71661bdf2..adee0c3a2 100644 --- a/libraries/botframework-streaming/botframework/streaming/protocol_adapter.py +++ b/libraries/botframework-streaming/botframework/streaming/protocol_adapter.py @@ -75,7 +75,7 @@ async def _on_receive_response(self, identifier: UUID, response: ReceiveResponse def _on_cancel_stream(self, content_stream_assembler: PayloadStreamAssembler): # TODO: on original C# code content_stream_assembler is typed as IAssembler - asyncio.create_task( + task = asyncio.create_task( self._send_operations.send_cancel_stream( content_stream_assembler.identifier ) From d6b5e6759d2d906afb47ed047a49e607b81a515a Mon Sep 17 00:00:00 2001 From: Takeshi YASHIRO Date: Sat, 27 Jan 2024 00:10:07 +0900 Subject: [PATCH 012/102] Treat HTTPException as it is, necessary to keep the original status code (#1939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Axel Suárez Co-authored-by: tracyboehrer --- .../aiohttp_channel_service_exception_middleware.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/botbuilder-core/botbuilder/core/integration/aiohttp_channel_service_exception_middleware.py b/libraries/botbuilder-core/botbuilder/core/integration/aiohttp_channel_service_exception_middleware.py index d58073d06..ef87d7489 100644 --- a/libraries/botbuilder-core/botbuilder/core/integration/aiohttp_channel_service_exception_middleware.py +++ b/libraries/botbuilder-core/botbuilder/core/integration/aiohttp_channel_service_exception_middleware.py @@ -5,6 +5,7 @@ from aiohttp.web import ( middleware, + HTTPException, HTTPNotImplemented, HTTPUnauthorized, HTTPNotFound, @@ -27,6 +28,8 @@ async def aiohttp_error_middleware(request, handler): raise HTTPUnauthorized() except KeyError: raise HTTPNotFound() + except HTTPException: + raise except Exception: traceback.print_exc() raise HTTPInternalServerError() From 7a03e6554735bd6ddaeca009720008b6e128796c Mon Sep 17 00:00:00 2001 From: ivopisarovic Date: Fri, 26 Jan 2024 16:11:11 +0100 Subject: [PATCH 013/102] Update aiohttp_telemetry_middleware.py (#1961) Fix: content-type may not be present in some requests, e.g. /robots.txt request. Co-authored-by: tracyboehrer --- .../applicationinsights/aiohttp/aiohttp_telemetry_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py index 30615f5c2..1a83612e6 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py @@ -19,7 +19,7 @@ def retrieve_aiohttp_body(): @middleware async def bot_telemetry_middleware(request, handler): """Process the incoming Flask request.""" - if "application/json" in request.headers["Content-Type"]: + if "Content-Type" in request.headers and request.headers["Content-Type"] == "application/json": body = await request.json() _REQUEST_BODIES[current_thread().ident] = body From 489d54729d233565813785ea7e4bc2e38a96de22 Mon Sep 17 00:00:00 2001 From: Ram Page <38049078+rampaged@users.noreply.github.com> Date: Fri, 26 Jan 2024 07:20:13 -0800 Subject: [PATCH 014/102] Ensure Connector Client is retrieved in teams_info.py (#2062) * Ensure Connector Client is retrieved in teams_info.py * Revert changes in cloud_adapter.py * Ensure Connector Client is retrieved in teams_info.py * Fixed teams member added test with mock ConnectorClient --------- Co-authored-by: tracyboehrer --- .../botbuilder/core/teams/teams_info.py | 11 ++++++++--- .../tests/teams/test_teams_activity_handler.py | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py index 3226cb053..1748aff0f 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py @@ -10,7 +10,7 @@ teams_get_meeting_info, teams_get_channel_data, ) -from botbuilder.core import CloudAdapterBase, BotFrameworkAdapter, TurnContext +from botbuilder.core import CloudAdapterBase, BotFrameworkAdapter, TurnContext, BotAdapter from botbuilder.schema import Activity, ConversationParameters, ConversationReference from botbuilder.schema.teams import ( ChannelInfo, @@ -318,10 +318,15 @@ def get_team_id(turn_context: TurnContext): @staticmethod async def _get_connector_client(turn_context: TurnContext) -> ConnectorClient: - return await turn_context.adapter.create_connector_client( - turn_context.activity.service_url + connector_client = turn_context.turn_state.get( + BotAdapter.BOT_CONNECTOR_CLIENT_KEY ) + if connector_client is None: + raise ValueError('This method requires a connector client.') + + return connector_client + @staticmethod async def _get_members( connector_client: ConnectorClient, conversation_id: str diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 69273b27c..15df4b9a2 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -592,6 +592,9 @@ async def test_on_teams_members_added_activity(self): turn_context = TurnContext(SimpleAdapter(), activity) + mock_connector_client = await SimpleAdapter.create_connector_client(self, turn_context.activity.service_url) + turn_context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = mock_connector_client + # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) From b4770e5d9c4dbd5889b0cc32bd180661a1ab86b9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 26 Jan 2024 09:45:11 -0600 Subject: [PATCH 015/102] djangorestframework bump (#2063) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-applicationinsights/setup.py | 2 +- .../botbuilder-core/botbuilder/core/teams/teams_info.py | 9 +++++++-- .../tests/teams/test_teams_activity_handler.py | 8 ++++++-- .../aiohttp/aiohttp_telemetry_middleware.py | 5 ++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libraries/botbuilder-applicationinsights/setup.py b/libraries/botbuilder-applicationinsights/setup.py index 87855043d..edd170b60 100644 --- a/libraries/botbuilder-applicationinsights/setup.py +++ b/libraries/botbuilder-applicationinsights/setup.py @@ -13,7 +13,7 @@ TESTS_REQUIRES = [ "aiounittest==1.3.0", "django==3.2.21", # For samples - "djangorestframework==3.10.3", # For samples + "djangorestframework==3.14.0", # For samples "flask==2.2.5", # For samples ] diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py index 1748aff0f..f70f6cccc 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py @@ -10,7 +10,12 @@ teams_get_meeting_info, teams_get_channel_data, ) -from botbuilder.core import CloudAdapterBase, BotFrameworkAdapter, TurnContext, BotAdapter +from botbuilder.core import ( + CloudAdapterBase, + BotFrameworkAdapter, + TurnContext, + BotAdapter, +) from botbuilder.schema import Activity, ConversationParameters, ConversationReference from botbuilder.schema.teams import ( ChannelInfo, @@ -323,7 +328,7 @@ async def _get_connector_client(turn_context: TurnContext) -> ConnectorClient: ) if connector_client is None: - raise ValueError('This method requires a connector client.') + raise ValueError("This method requires a connector client.") return connector_client diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 15df4b9a2..9a1872952 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -592,8 +592,12 @@ async def test_on_teams_members_added_activity(self): turn_context = TurnContext(SimpleAdapter(), activity) - mock_connector_client = await SimpleAdapter.create_connector_client(self, turn_context.activity.service_url) - turn_context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = mock_connector_client + mock_connector_client = await SimpleAdapter.create_connector_client( + self, turn_context.activity.service_url + ) + turn_context.turn_state[ + BotAdapter.BOT_CONNECTOR_CLIENT_KEY + ] = mock_connector_client # Act bot = TestingTeamsActivityHandler() diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py index 1a83612e6..d5dc7e2eb 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/aiohttp_telemetry_middleware.py @@ -19,7 +19,10 @@ def retrieve_aiohttp_body(): @middleware async def bot_telemetry_middleware(request, handler): """Process the incoming Flask request.""" - if "Content-Type" in request.headers and request.headers["Content-Type"] == "application/json": + if ( + "Content-Type" in request.headers + and request.headers["Content-Type"] == "application/json" + ): body = await request.json() _REQUEST_BODIES[current_thread().ident] = body From e98d4069fa516a816c3b8074d81a4f6c82411318 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 8 Feb 2024 09:07:16 -0600 Subject: [PATCH 016/102] Update botbuilder-python-ci.yml for Azure Pipelines Changed Python build versions --- pipelines/botbuilder-python-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index eecc05394..8d55cd230 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -6,8 +6,9 @@ variables: COVERALLS_GIT_COMMIT: $(Build.SourceVersion) COVERALLS_SERVICE_JOB_ID: $(Build.BuildId) COVERALLS_SERVICE_NAME: python-ci - python.37: 3.7 python.38: 3.8 + python.39: 3.9 + python.310: 3.10 python.311: 3.11 # PythonCoverallsToken: get this from Azure @@ -20,10 +21,12 @@ jobs: strategy: matrix: - Python37: - PYTHON_VERSION: '$(python.37)' Python38: PYTHON_VERSION: '$(python.38)' + Python39: + PYTHON_VERSION: '$(python.39)' + Python310: + PYTHON_VERSION: '$(python.310)' Python311: PYTHON_VERSION: '$(python.311)' maxParallel: 3 From 004a286b6bd042c56f33a9c77d1eee3a5c8f3fb2 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 8 Feb 2024 13:31:22 -0600 Subject: [PATCH 017/102] aiohttp to 3.9.3 (#2069) * aiohttp to 3.9.2 * aiohttp to 3.9.3 * Update botbuilder-python-ci.yml for Azure Pipelines Changed Python build versions * Updated README --------- Co-authored-by: Tracy Boehrer --- README.md | 4 ++-- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-ai/setup.py | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../setup.py | 2 +- libraries/botframework-connector/tests/requirements.txt | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4b7d3ff4f..df939dd52 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains code for the Python version of the [Microsoft Bot Framework SDK](https://github.com/Microsoft/botframework-sdk), which is part of the Microsoft Bot Framework - a comprehensive framework for building enterprise-grade conversational AI experiences. -This SDK enables developers to model conversation and build sophisticated bot applications using Python. SDKs for [JavaScript](https://github.com/Microsoft/botbuilder-js), [.NET](https://github.com/Microsoft/botbuilder-dotnet) and [Java (preview)](https://github.com/Microsoft/botbuilder-java) are also available. +This SDK enables developers to model conversation and build sophisticated bot applications using Python. SDKs for [JavaScript](https://github.com/Microsoft/botbuilder-js) and [.NET](https://github.com/Microsoft/botbuilder-dotnet) are also available. To get started building bots using the SDK, see the [Azure Bot Service Documentation](https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0). @@ -43,7 +43,7 @@ If you want to debug an issue, would like to [contribute](#contributing-code), o ### Prerequisites - [Git](https://git-scm.com/downloads) -- [Python 3.8.2](https://www.python.org/downloads/) +- [Python 3.8.17](https://www.python.org/downloads/) Python "Virtual Environments" allow Python packages to be installed in an isolated location for a particular application, rather than being installed globally, as such it is common practice to use them. Click [here](https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments) to learn more about creating _and activating_ Virtual Environments in Python. diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index d157c7b0e..312d38e8a 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.9.0 +aiohttp==3.9.3 pyslack botbuilder-core==4.15.0 slackclient diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 476d9a085..0b61efc81 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.15.0", "botbuilder-core==4.15.0", - "aiohttp==3.8.5", + "aiohttp==3.9.3", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 558ae91d3..85b38ee6f 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.15.0 botbuilder-schema==4.15.0 -aiohttp==3.9.0 +aiohttp==3.9.3 diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index 5ab268470..b69be5e90 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.15.0", "botbuilder-core==4.15.0", "yarl>=1.8.1", - "aiohttp==3.8.5", + "aiohttp==3.9.3", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index f6b661c88..f4b9933b8 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.8.5", + "aiohttp==3.9.3", "botbuilder-schema==4.15.0", "botframework-connector==4.15.0", "botbuilder-core==4.15.0", 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 From 6f8f7ab466ddbbf658b4c399525b7d04721feb22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:37:12 -0600 Subject: [PATCH 018/102] Bump cryptography from 41.0.6 to 42.0.0 in /libraries/botbuilder-dialogs (#2068) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.6 to 42.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.6...42.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-dialogs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 3d2fd9080..292d949cf 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botbuilder-core==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.6 +cryptography==42.0.0 aiounittest==1.3.0 From 711ccbff98116465e3808e968ca900e2feeb9455 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:50:36 -0600 Subject: [PATCH 019/102] Bump cryptography in /libraries/botframework-connector (#2067) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.6 to 42.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.6...42.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botframework-connector/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 0b30023a1..afc85f82a 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -2,5 +2,5 @@ msrest==0.7.* botbuilder-schema==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.6 +cryptography==42.0.0 msal==1.* From e80386c7ca5a9c30ef33cc359265fa10a34bb34c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:02:38 -0600 Subject: [PATCH 020/102] Bump cryptography from 41.0.6 to 42.0.0 in /libraries/botbuilder-core (#2066) Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.6 to 42.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.6...42.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-core/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 5a8a126bb..6e19d06ff 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botframework-streaming==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==41.0.6 +cryptography==42.0.0 aiounittest==1.3.0 \ No newline at end of file From 1f4a53707d1a9e27f40c91874bdb2295c3a02607 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 9 Feb 2024 07:58:36 -0600 Subject: [PATCH 021/102] django to 3.2.24 (#2070) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-applicationinsights/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-applicationinsights/setup.py b/libraries/botbuilder-applicationinsights/setup.py index edd170b60..a91a9acb1 100644 --- a/libraries/botbuilder-applicationinsights/setup.py +++ b/libraries/botbuilder-applicationinsights/setup.py @@ -12,7 +12,7 @@ ] TESTS_REQUIRES = [ "aiounittest==1.3.0", - "django==3.2.21", # For samples + "django==3.2.24", # For samples "djangorestframework==3.14.0", # For samples "flask==2.2.5", # For samples ] From 525a79cff0d0fadce6ef9ffc437e62a30e19c185 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 12 Feb 2024 08:27:33 -0600 Subject: [PATCH 022/102] Moved non-library code out of libraries (#2072) Co-authored-by: Tracy Boehrer --- {libraries/swagger => swagger}/.gitignore | 0 {libraries/swagger => swagger}/ConnectorAPI.json | 0 {libraries/swagger => swagger}/README.md | 0 {libraries/swagger => swagger}/TokenAPI.json | 0 {libraries/swagger => swagger}/generateClient.cmd | 0 {libraries/swagger => swagger}/package-lock.json | 0 {libraries/swagger => swagger}/package.json | 0 {libraries/swagger => swagger}/tokenAPI.md | 0 .../functional-tests/functionaltestbot/Dockerfile | 0 {libraries => tests}/functional-tests/functionaltestbot/Dockfile | 0 .../functional-tests/functionaltestbot/client_driver/README.md | 0 .../functional-tests/functionaltestbot/flask_bot_app/__init__.py | 0 .../functional-tests/functionaltestbot/flask_bot_app/app.py | 0 .../functional-tests/functionaltestbot/flask_bot_app/bot_app.py | 0 .../functionaltestbot/flask_bot_app/default_config.py | 0 .../functional-tests/functionaltestbot/flask_bot_app/my_bot.py | 0 .../functionaltestbot/functionaltestbot/README.md | 0 .../functional-tests/functionaltestbot/functionaltestbot/about.py | 0 .../functional-tests/functionaltestbot/functionaltestbot/app.py | 0 .../functional-tests/functionaltestbot/functionaltestbot/bot.py | 0 .../functionaltestbot/functionaltestbot/config.py | 0 .../functionaltestbot/functionaltestbot/requirements.txt | 0 {libraries => tests}/functional-tests/functionaltestbot/init.sh | 0 .../functional-tests/functionaltestbot/requirements.txt | 0 .../functional-tests/functionaltestbot/runserver.py | 0 {libraries => tests}/functional-tests/functionaltestbot/setup.py | 0 .../functional-tests/functionaltestbot/sshd_config | 0 .../functionaltestbot/template/linux/template.json | 0 {libraries => tests}/functional-tests/functionaltestbot/test.sh | 0 {libraries => tests}/functional-tests/requirements.txt | 0 {libraries => tests}/functional-tests/tests/direct_line_client.py | 0 {libraries => tests}/functional-tests/tests/test_py_bot.py | 0 {libraries => tests}/functional-tests/tests/test_slack_client.py | 0 33 files changed, 0 insertions(+), 0 deletions(-) rename {libraries/swagger => swagger}/.gitignore (100%) rename {libraries/swagger => swagger}/ConnectorAPI.json (100%) rename {libraries/swagger => swagger}/README.md (100%) rename {libraries/swagger => swagger}/TokenAPI.json (100%) rename {libraries/swagger => swagger}/generateClient.cmd (100%) rename {libraries/swagger => swagger}/package-lock.json (100%) rename {libraries/swagger => swagger}/package.json (100%) rename {libraries/swagger => swagger}/tokenAPI.md (100%) rename {libraries => tests}/functional-tests/functionaltestbot/Dockerfile (100%) rename {libraries => tests}/functional-tests/functionaltestbot/Dockfile (100%) rename {libraries => tests}/functional-tests/functionaltestbot/client_driver/README.md (100%) rename {libraries => tests}/functional-tests/functionaltestbot/flask_bot_app/__init__.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/flask_bot_app/app.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/flask_bot_app/bot_app.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/flask_bot_app/default_config.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/flask_bot_app/my_bot.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/README.md (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/about.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/app.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/bot.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/config.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/functionaltestbot/requirements.txt (100%) rename {libraries => tests}/functional-tests/functionaltestbot/init.sh (100%) rename {libraries => tests}/functional-tests/functionaltestbot/requirements.txt (100%) rename {libraries => tests}/functional-tests/functionaltestbot/runserver.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/setup.py (100%) rename {libraries => tests}/functional-tests/functionaltestbot/sshd_config (100%) rename {libraries => tests}/functional-tests/functionaltestbot/template/linux/template.json (100%) rename {libraries => tests}/functional-tests/functionaltestbot/test.sh (100%) rename {libraries => tests}/functional-tests/requirements.txt (100%) rename {libraries => tests}/functional-tests/tests/direct_line_client.py (100%) rename {libraries => tests}/functional-tests/tests/test_py_bot.py (100%) rename {libraries => tests}/functional-tests/tests/test_slack_client.py (100%) diff --git a/libraries/swagger/.gitignore b/swagger/.gitignore similarity index 100% rename from libraries/swagger/.gitignore rename to swagger/.gitignore diff --git a/libraries/swagger/ConnectorAPI.json b/swagger/ConnectorAPI.json similarity index 100% rename from libraries/swagger/ConnectorAPI.json rename to swagger/ConnectorAPI.json diff --git a/libraries/swagger/README.md b/swagger/README.md similarity index 100% rename from libraries/swagger/README.md rename to swagger/README.md diff --git a/libraries/swagger/TokenAPI.json b/swagger/TokenAPI.json similarity index 100% rename from libraries/swagger/TokenAPI.json rename to swagger/TokenAPI.json diff --git a/libraries/swagger/generateClient.cmd b/swagger/generateClient.cmd similarity index 100% rename from libraries/swagger/generateClient.cmd rename to swagger/generateClient.cmd diff --git a/libraries/swagger/package-lock.json b/swagger/package-lock.json similarity index 100% rename from libraries/swagger/package-lock.json rename to swagger/package-lock.json diff --git a/libraries/swagger/package.json b/swagger/package.json similarity index 100% rename from libraries/swagger/package.json rename to swagger/package.json diff --git a/libraries/swagger/tokenAPI.md b/swagger/tokenAPI.md similarity index 100% rename from libraries/swagger/tokenAPI.md rename to swagger/tokenAPI.md diff --git a/libraries/functional-tests/functionaltestbot/Dockerfile b/tests/functional-tests/functionaltestbot/Dockerfile similarity index 100% rename from libraries/functional-tests/functionaltestbot/Dockerfile rename to tests/functional-tests/functionaltestbot/Dockerfile diff --git a/libraries/functional-tests/functionaltestbot/Dockfile b/tests/functional-tests/functionaltestbot/Dockfile similarity index 100% rename from libraries/functional-tests/functionaltestbot/Dockfile rename to tests/functional-tests/functionaltestbot/Dockfile diff --git a/libraries/functional-tests/functionaltestbot/client_driver/README.md b/tests/functional-tests/functionaltestbot/client_driver/README.md similarity index 100% rename from libraries/functional-tests/functionaltestbot/client_driver/README.md rename to tests/functional-tests/functionaltestbot/client_driver/README.md diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/__init__.py b/tests/functional-tests/functionaltestbot/flask_bot_app/__init__.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/flask_bot_app/__init__.py rename to tests/functional-tests/functionaltestbot/flask_bot_app/__init__.py diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/app.py b/tests/functional-tests/functionaltestbot/flask_bot_app/app.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/flask_bot_app/app.py rename to tests/functional-tests/functionaltestbot/flask_bot_app/app.py diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py b/tests/functional-tests/functionaltestbot/flask_bot_app/bot_app.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/flask_bot_app/bot_app.py rename to tests/functional-tests/functionaltestbot/flask_bot_app/bot_app.py diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py b/tests/functional-tests/functionaltestbot/flask_bot_app/default_config.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/flask_bot_app/default_config.py rename to tests/functional-tests/functionaltestbot/flask_bot_app/default_config.py diff --git a/libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py b/tests/functional-tests/functionaltestbot/flask_bot_app/my_bot.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/flask_bot_app/my_bot.py rename to tests/functional-tests/functionaltestbot/flask_bot_app/my_bot.py diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/README.md b/tests/functional-tests/functionaltestbot/functionaltestbot/README.md similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/README.md rename to tests/functional-tests/functionaltestbot/functionaltestbot/README.md diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/about.py b/tests/functional-tests/functionaltestbot/functionaltestbot/about.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/about.py rename to tests/functional-tests/functionaltestbot/functionaltestbot/about.py diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/app.py b/tests/functional-tests/functionaltestbot/functionaltestbot/app.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/app.py rename to tests/functional-tests/functionaltestbot/functionaltestbot/app.py diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py b/tests/functional-tests/functionaltestbot/functionaltestbot/bot.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/bot.py rename to tests/functional-tests/functionaltestbot/functionaltestbot/bot.py diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/config.py b/tests/functional-tests/functionaltestbot/functionaltestbot/config.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/config.py rename to tests/functional-tests/functionaltestbot/functionaltestbot/config.py diff --git a/libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt b/tests/functional-tests/functionaltestbot/functionaltestbot/requirements.txt similarity index 100% rename from libraries/functional-tests/functionaltestbot/functionaltestbot/requirements.txt rename to tests/functional-tests/functionaltestbot/functionaltestbot/requirements.txt diff --git a/libraries/functional-tests/functionaltestbot/init.sh b/tests/functional-tests/functionaltestbot/init.sh similarity index 100% rename from libraries/functional-tests/functionaltestbot/init.sh rename to tests/functional-tests/functionaltestbot/init.sh diff --git a/libraries/functional-tests/functionaltestbot/requirements.txt b/tests/functional-tests/functionaltestbot/requirements.txt similarity index 100% rename from libraries/functional-tests/functionaltestbot/requirements.txt rename to tests/functional-tests/functionaltestbot/requirements.txt diff --git a/libraries/functional-tests/functionaltestbot/runserver.py b/tests/functional-tests/functionaltestbot/runserver.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/runserver.py rename to tests/functional-tests/functionaltestbot/runserver.py diff --git a/libraries/functional-tests/functionaltestbot/setup.py b/tests/functional-tests/functionaltestbot/setup.py similarity index 100% rename from libraries/functional-tests/functionaltestbot/setup.py rename to tests/functional-tests/functionaltestbot/setup.py diff --git a/libraries/functional-tests/functionaltestbot/sshd_config b/tests/functional-tests/functionaltestbot/sshd_config similarity index 100% rename from libraries/functional-tests/functionaltestbot/sshd_config rename to tests/functional-tests/functionaltestbot/sshd_config diff --git a/libraries/functional-tests/functionaltestbot/template/linux/template.json b/tests/functional-tests/functionaltestbot/template/linux/template.json similarity index 100% rename from libraries/functional-tests/functionaltestbot/template/linux/template.json rename to tests/functional-tests/functionaltestbot/template/linux/template.json diff --git a/libraries/functional-tests/functionaltestbot/test.sh b/tests/functional-tests/functionaltestbot/test.sh similarity index 100% rename from libraries/functional-tests/functionaltestbot/test.sh rename to tests/functional-tests/functionaltestbot/test.sh diff --git a/libraries/functional-tests/requirements.txt b/tests/functional-tests/requirements.txt similarity index 100% rename from libraries/functional-tests/requirements.txt rename to tests/functional-tests/requirements.txt diff --git a/libraries/functional-tests/tests/direct_line_client.py b/tests/functional-tests/tests/direct_line_client.py similarity index 100% rename from libraries/functional-tests/tests/direct_line_client.py rename to tests/functional-tests/tests/direct_line_client.py diff --git a/libraries/functional-tests/tests/test_py_bot.py b/tests/functional-tests/tests/test_py_bot.py similarity index 100% rename from libraries/functional-tests/tests/test_py_bot.py rename to tests/functional-tests/tests/test_py_bot.py diff --git a/libraries/functional-tests/tests/test_slack_client.py b/tests/functional-tests/tests/test_slack_client.py similarity index 100% rename from libraries/functional-tests/tests/test_slack_client.py rename to tests/functional-tests/tests/test_slack_client.py From 184a2dab772b97b91c914a3dd88d3ab876298c23 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 12 Feb 2024 08:54:59 -0600 Subject: [PATCH 023/102] Skipping Slack test if env not setup (#2073) Co-authored-by: Tracy Boehrer --- .../botbuilder-adapters-slack}/tests/test_slack_client.py | 4 ++++ 1 file changed, 4 insertions(+) rename {tests/functional-tests => libraries/botbuilder-adapters-slack}/tests/test_slack_client.py (96%) diff --git a/tests/functional-tests/tests/test_slack_client.py b/libraries/botbuilder-adapters-slack/tests/test_slack_client.py similarity index 96% rename from tests/functional-tests/tests/test_slack_client.py rename to libraries/botbuilder-adapters-slack/tests/test_slack_client.py index ab2a9ca90..9174058a5 100644 --- a/tests/functional-tests/tests/test_slack_client.py +++ b/libraries/botbuilder-adapters-slack/tests/test_slack_client.py @@ -10,9 +10,13 @@ import time import aiounittest import requests +import pytest + +SKIP = os.getenv("SlackChannel") == '' class SlackClient(aiounittest.AsyncTestCase): + @pytest.mark.skipif(not SKIP, reason="Needs the env.SlackChannel to run.") async def test_send_and_receive_slack_message(self): # Arrange echo_guid = str(uuid.uuid4()) From a0780275ba41fd3950ae1583169e86841d6e1a08 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 13 Feb 2024 10:05:33 -0600 Subject: [PATCH 024/102] SingleTenant support (#2055) * SingleTenant support * Pylint corrections * Black correction * SingleTenant Gov correction * Ported Gov SingleTenant fixes from DotNet * Black correction * Pylint corrections * Black corrections for app_credentials * Corrected AppCredentials._should_set_token * Changed auth constant to match setting name * black corrections --------- Co-authored-by: Tracy Boehrer --- doc/SkillClaimsValidation.md | 8 ++ .../tests/test_slack_client.py | 2 +- .../botbuilder/core/bot_framework_adapter.py | 13 ---- ...tion_service_client_credentials_factory.py | 19 ----- .../tests/test_bot_framework_adapter.py | 12 --- .../aiohttp/bot_framework_http_adapter.py | 6 -- ...ation_service_client_credential_factory.py | 38 +++++++++- .../auth/_bot_framework_client_impl.py | 5 +- .../_built_in_bot_framework_authentication.py | 2 +- ...ment_cloud_bot_framework_authentication.py | 2 +- ...ameterized_bot_framework_authentication.py | 12 ++- .../connector/auth/app_credentials.py | 74 +++++++++---------- .../auth/authentication_configuration.py | 27 +++++++ .../auth/authentication_constants.py | 24 +++++- .../connector/auth/channel_validation.py | 2 +- .../connector/auth/emulator_validation.py | 4 +- .../auth/government_channel_validation.py | 2 +- .../connector/auth/government_constants.py | 13 +++- .../connector/auth/jwt_token_validation.py | 6 +- .../auth/microsoft_app_credentials.py | 12 --- .../microsoft_government_app_credentials.py | 21 ++++-- ...sword_service_client_credential_factory.py | 66 ++++++++--------- .../service_client_credentials_factory.py | 6 +- .../connector/auth/skill_validation.py | 43 ++++++----- .../botframework-connector/tests/test_auth.py | 48 ------------ 25 files changed, 234 insertions(+), 233 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-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/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/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-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/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/_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/_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/_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/_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..b054f0c2f 100644 --- a/libraries/botframework-connector/botframework/connector/auth/app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/app_credentials.py @@ -1,13 +1,10 @@ # 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 -from botframework.connector.auth import AuthenticationConstants +from .authentication_constants import AuthenticationConstants class AppCredentials(Authentication): @@ -17,16 +14,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 +27,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: + 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. :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)) + def _is_trusted_url(host: str) -> bool: # pylint: disable=unused-argument + """ + 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 +86,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,13 +95,13 @@ 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. 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: 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..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,10 +56,30 @@ 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" ) + # 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/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 c15c8e41e..6574859eb 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -14,11 +14,20 @@ 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" + """ TO CHANNEL FROM BOT: OAuth scope to request """ @@ -37,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/jwt_token_validation.py b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py index e4cbddd39..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 @@ -76,9 +75,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 @@ -115,7 +111,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..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 @@ -14,9 +13,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, @@ -73,11 +69,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 eb59fe941..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 @@ -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,22 @@ 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, ) @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 1e14b496c..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 @@ -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() @@ -34,44 +45,32 @@ 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( 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) + elif normalized_endpoint.startswith( + GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + ): + 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 +81,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..b708e27cb 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: """ @@ -114,13 +98,34 @@ 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( + issuer=[ + "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), + 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, ) diff --git a/libraries/botframework-connector/tests/test_auth.py b/libraries/botframework-connector/tests/test_auth.py index 39a29a1ea..cc3abf66a 100644 --- a/libraries/botframework-connector/tests/test_auth.py +++ b/libraries/botframework-connector/tests/test_auth.py @@ -352,54 +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): credentials = SimpleCredentialProvider("", "") From 1fa6d4ae7ed244015b5bc7679f0cced959df509e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:05:04 -0600 Subject: [PATCH 025/102] Bump cryptography from 42.0.0 to 42.0.4 in /libraries/botbuilder-core (#2079) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.0...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-core/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 6e19d06ff..8da13ad95 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botframework-streaming==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==42.0.0 +cryptography==42.0.4 aiounittest==1.3.0 \ No newline at end of file From ef873b87622d3b626cfd3a85db4e616e021cccfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:01:43 -0600 Subject: [PATCH 026/102] Bump cryptography in /libraries/botframework-connector (#2080) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.0...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botframework-connector/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index afc85f82a..25d38e349 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -2,5 +2,5 @@ msrest==0.7.* botbuilder-schema==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==42.0.0 +cryptography==42.0.4 msal==1.* From 4acc0ea4157d95c018925db72ed3ce8832298641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:36:22 -0600 Subject: [PATCH 027/102] Bump cryptography from 42.0.0 to 42.0.4 in /libraries/botbuilder-dialogs (#2081) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.0...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-dialogs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 292d949cf..05ccb8570 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.15.0 botbuilder-core==4.15.0 requests==2.31.0 PyJWT==2.4.0 -cryptography==42.0.0 +cryptography==42.0.4 aiounittest==1.3.0 From 8f1b58d3ff3dd5d2740ab47a1e460372ac88e40a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20Andr=C3=A9?= Date: Wed, 6 Mar 2024 14:58:03 +0100 Subject: [PATCH 028/102] fix TeamsChannelAccount model (#2083) --- .../botbuilder-schema/botbuilder/schema/teams/_models_py3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 3f0f9689f..bb860c8da 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -1903,7 +1903,7 @@ class TeamsChannelAccount(ChannelAccount): "surname": {"key": "surname", "type": "str"}, "email": {"key": "email", "type": "str"}, "user_principal_name": {"key": "userPrincipalName", "type": "str"}, - "aad_object_id": {"key": "objectId", "type": "str"}, + "aad_object_id": {"key": "aadObjectId", "type": "str"}, "tenant_id": {"key": "tenantId", "type": "str"}, "user_role": {"key": "userRole", "type": "str"}, } From 83854104fedef22160f53520cc36f266228a73ee Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 7 Mar 2024 13:26:05 -0600 Subject: [PATCH 029/102] Test correction for SM01307 (#2085) Co-authored-by: Tracy Boehrer --- .../botbuilder-applicationinsights/django_tests/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-applicationinsights/django_tests/views.py b/libraries/botbuilder-applicationinsights/django_tests/views.py index c91c0bbb5..43653cef7 100644 --- a/libraries/botbuilder-applicationinsights/django_tests/views.py +++ b/libraries/botbuilder-applicationinsights/django_tests/views.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import html from rest_framework.decorators import api_view from botbuilder.applicationinsights.django import common from botbuilder.applicationinsights import ApplicationInsightsTelemetryClient @@ -42,7 +43,7 @@ def errorer(request): def echoer(request): - return HttpResponse(request.appinsights.request.id) + return HttpResponse(html.escape(request.appinsights.request.id)) @api_view(["POST"]) @@ -52,12 +53,12 @@ def getid(request, id): @api_view(["POST"]) def returncode(request, id): - return HttpResponse("returncode", status=int(id)) + return HttpResponse("returncode", status=int(html.escape(id))) @api_view(["POST"]) def templater(request, data): - return TemplateResponse(request, "template.html", {"context": data}) + return TemplateResponse(request, "template.html", {"context": html.escape(data)}) class classview: From 5fee356c98c9fffff1cedb7add9e54d454130a84 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 12 Mar 2024 09:41:34 -0500 Subject: [PATCH 030/102] Removed unused tests (#2087) Co-authored-by: Tracy Boehrer --- .../django_tests/.gitignore | 1 - .../django_tests/README.md | 23 - .../django_tests/all_tests.sh | 58 -- .../django_tests/run_test.sh | 32 - .../django_tests/template.html | 1 - .../django_tests/tests.py | 588 ------------------ .../django_tests/urls.py | 19 - .../django_tests/views.py | 66 -- 8 files changed, 788 deletions(-) delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/.gitignore delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/README.md delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/all_tests.sh delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/run_test.sh delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/template.html delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/tests.py delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/urls.py delete mode 100644 libraries/botbuilder-applicationinsights/django_tests/views.py diff --git a/libraries/botbuilder-applicationinsights/django_tests/.gitignore b/libraries/botbuilder-applicationinsights/django_tests/.gitignore deleted file mode 100644 index e84001e2c..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -aitest diff --git a/libraries/botbuilder-applicationinsights/django_tests/README.md b/libraries/botbuilder-applicationinsights/django_tests/README.md deleted file mode 100644 index bbc0b9db3..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# DJANGO-specific tests -Django generates *code* to create projects (`django-admin startproject`) and apps. For testing, we test the generated code. The tests are bare-bones to be compatible across different versions of django. - -- This project contains a script to execute tests against currently supported version(s) of python and django. -- Assume latest version of Application Insights. -- Relies on virtualenv to run all tests. -- Uses django commands to generate new project and execute django tests. -- To run, first `cd django_tests` and then `bash .\all_tests.sh` (ie, in Powershell) to run all permutations. - -File | | Description ---- | --- -all_tests.sh | Runs our current test matrix of python/django versions. Current matrix is python (3.7) and django (2.2). -README.md | This file. -run_test.sh | Runs specific python/django version to create project, copy replacement files and runs tests. -template.html | Template file -tests.py | Django tests. -urls.py | url paths called by tests -views.py | paths that are called - - - - - diff --git a/libraries/botbuilder-applicationinsights/django_tests/all_tests.sh b/libraries/botbuilder-applicationinsights/django_tests/all_tests.sh deleted file mode 100644 index 562785cf2..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/all_tests.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -if [ -z $PYTHON ]; then - PYTHON=$(which python) -fi - -cd $(dirname $0) -BASEDIR=$(pwd) - -# Django/python compatibility matrix... -if $PYTHON -c "import sys; sys.exit(1 if (sys.version_info.major == 3 and sys.version_info.minor == 6) else 0)"; then - echo "[Error] Environment should be configured with Python 3.7!" 1>&2 - exit 2 -fi -# Add more versions here (space delimited). -DJANGO_VERSIONS='2.2' - -# For each Django version... -for v in $DJANGO_VERSIONS -do - echo "" - echo "***" - echo "*** Running tests for Django $v" - echo "***" - echo "" - - # Create new directory - TMPDIR=$(mktemp -d) - function cleanup - { - rm -rf $TMPDIR - exit $1 - } - - trap cleanup EXIT SIGINT - - # Create virtual environment - $PYTHON -m venv $TMPDIR/env - - # Install Django version + application insights - . $TMPDIR/env/bin/activate - pip install Django==$v || exit $? - cd $BASEDIR/.. - pip install . || exit $? - - # Run tests - cd $BASEDIR - bash ./run_test.sh || exit $? - - # Deactivate - # (Windows may complain since doesn't add deactivate to path properly) - deactivate - - # Remove venv - rm -rf $TMPDIR -done \ No newline at end of file diff --git a/libraries/botbuilder-applicationinsights/django_tests/run_test.sh b/libraries/botbuilder-applicationinsights/django_tests/run_test.sh deleted file mode 100644 index 3144a2684..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/run_test.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# It is expected at this point that django and applicationinsights are both installed into a -# virtualenv. -django_version=$(python -c "import django ; print('.'.join(map(str, django.VERSION[0:2])))") -test $? -eq 0 || exit 1 - -# Create a new temporary work directory -TMPDIR=$(mktemp -d) -SRCDIR=$(pwd) -function cleanup -{ - cd $SRCDIR - rm -rf $TMPDIR - exit $1 -} -trap cleanup EXIT SIGINT -cd $TMPDIR - -# Set up Django project -django-admin startproject aitest -cd aitest -cp $SRCDIR/views.py aitest/views.py -cp $SRCDIR/tests.py aitest/tests.py -cp $SRCDIR/urls.py aitest/urls.py -cp $SRCDIR/template.html aitest/template.html - -./manage.py test -exit $? \ No newline at end of file diff --git a/libraries/botbuilder-applicationinsights/django_tests/template.html b/libraries/botbuilder-applicationinsights/django_tests/template.html deleted file mode 100644 index 0ce23e725..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/template.html +++ /dev/null @@ -1 +0,0 @@ -Test django template: {{ context }} \ No newline at end of file diff --git a/libraries/botbuilder-applicationinsights/django_tests/tests.py b/libraries/botbuilder-applicationinsights/django_tests/tests.py deleted file mode 100644 index ba2fdc912..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/tests.py +++ /dev/null @@ -1,588 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import logging -import os - -import django -from applicationinsights.channel import ( - AsynchronousSender, - NullSender, - SenderBase, - SynchronousQueue, - TelemetryChannel, -) -from applicationinsights.channel.SenderBase import ( - DEFAULT_ENDPOINT_URL as DEFAULT_ENDPOINT, -) -from botbuilder.applicationinsights.django import common -from django.test import TestCase, modify_settings, override_settings -from rest_framework.test import RequestsClient - - -# Note: MIDDLEWARE_CLASSES for django.VERSION <= (1, 10) -MIDDLEWARE_NAME = "MIDDLEWARE" -TEST_IKEY = "12345678-1234-5678-9012-123456789abc" -TEST_ENDPOINT = "https://test.endpoint/v2/track" -PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) - - -class AITestCase(TestCase): - def plug_sender(self): - # Reset saved objects - common.saved_clients = {} - common.saved_channels = {} - - # Create a client and mock out the sender - client = common.create_client() - sender = MockSender() - client._channel = TelemetryChannel(None, SynchronousQueue(sender)) - # client.add_telemetry_processor(bot_telemetry_processor) - self.events = sender.events - self.channel = client.channel - - def get_events(self, count): - self.channel.flush() - self.assertEqual( - len(self.events), - count, - "Expected %d event(s) in queue (%d actual)" % (count, len(self.events)), - ) - if count == 1: - return self.events[0] - return self.events - - -@modify_settings( - **{ - MIDDLEWARE_NAME: { - "append": "botbuilder.applicationinsights.django.ApplicationInsightsMiddleware", - "prepend": "botbuilder.applicationinsights.django.BotTelemetryMiddleware", - } - } -) -@override_settings( - APPLICATION_INSIGHTS={"ikey": TEST_IKEY}, - # Templates for 1.7 - TEMPLATE_DIRS=(PROJECT_ROOT,), - TEMPLATE_LOADERS=("django.template.loaders.filesystem.Loader",), - # Templates for 1.8 and up - TEMPLATES=[ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [PROJECT_ROOT], - } - ], -) -class MiddlewareTests(AITestCase): - def setUp(self): - self.plug_sender() - - def test_basic_request(self): - """Tests that hitting a simple view generates a telemetry item with the correct properties""" - response = self.invoke_post("") - assert response.status_code == 200 - - event = self.get_events(1) - tags = event["tags"] - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(event["iKey"], TEST_IKEY) - self.assertEqual(tags["ai.operation.name"], "POST /", "Operation name") - self.assertEqual(data["name"], "POST /", "Request name") - self.assertEqual(data["responseCode"], 200, "Status code") - self.assertEqual(data["success"], True, "Success value") - self.assertEqual(data["url"], "http://testserver/", "Request url") - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - def test_bot_event(self): - """Tests that traces logged from inside of a view are submitted and parented to the request telemetry item""" - response = self.invoke_post("botlog_event") - assert response.status_code == 200 - - logev, reqev = self.get_events(2) - - # Check request event (minimal, since we validate this elsewhere) - tags = reqev["tags"] - data = reqev["data"]["baseData"] - reqid = tags["ai.operation.id"] - self.assertEqual( - reqev["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(data["id"], reqid, "Request id") - self.assertEqual(data["name"], "POST /botlog_event", "Operation name") - self.assertEqual(data["url"], "http://testserver/botlog_event", "Request url") - - self.assertTrue(reqid, "Request id not empty") - - # Check log event - tags = logev["tags"] - data = logev["data"]["baseData"] - self.assertEqual( - logev["name"], "Microsoft.ApplicationInsights.Event", "Event type" - ) - self.assertEqual(logev["iKey"], TEST_IKEY) - self.assertEqual(data["name"], "botevent", "validate event name") - self.assertEqual(data["properties"]["foo"], "bar", "foo=bar") - self.assertEqual(data["properties"]["moo"], "cow", "moo=cow") - # Test TelemetryProcessor properties - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - def test_logger(self): - """Tests that traces logged from inside of a view are submitted and parented to the request telemetry item""" - response = self.invoke_post("logger") - self.assertEqual(response.status_code, 200) - - logev, reqev = self.get_events(2) - - # Check request event (minimal, since we validate this elsewhere) - tags = reqev["tags"] - data = reqev["data"]["baseData"] - reqid = tags["ai.operation.id"] - self.assertEqual( - reqev["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(data["id"], reqid, "Request id") - self.assertEqual(data["name"], "POST /logger", "Operation name") - self.assertEqual(data["url"], "http://testserver/logger", "Request url") - self.assertTrue(reqid, "Request id not empty") - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - # Check log event - tags = logev["tags"] - data = logev["data"]["baseData"] - self.assertEqual( - logev["name"], "Microsoft.ApplicationInsights.Message", "Event type" - ) - self.assertEqual(logev["iKey"], TEST_IKEY) - self.assertEqual(tags["ai.operation.parentId"], reqid, "Parent id") - self.assertEqual(data["message"], "Logger message", "Log message") - self.assertEqual(data["properties"]["property"], "value", "Property=value") - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - def test_thrower(self): - """ - Tests that unhandled exceptions generate an exception telemetry item parented to the request telemetry item - """ - response = self.invoke_post("thrower") - self.assertEqual(response.status_code, 500) - - errev, reqev = self.get_events(2) - # reqev = self.get_events(1) - - # Check request event - tags = reqev["tags"] - data = reqev["data"]["baseData"] - reqid = tags["ai.operation.id"] - self.assertEqual( - reqev["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(reqev["iKey"], TEST_IKEY) - self.assertEqual(data["id"], reqid, "Request id") - self.assertEqual(data["responseCode"], 500, "Response code") - self.assertEqual(data["success"], False, "Success value") - self.assertEqual(data["name"], "POST /thrower", "Request name") - self.assertEqual(data["url"], "http://testserver/thrower", "Request url") - self.assertTrue(reqid, "Request id not empty") - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - # Check exception event - tags = errev["tags"] - data = errev["data"]["baseData"] - self.assertEqual( - errev["name"], "Microsoft.ApplicationInsights.Exception", "Event type" - ) - self.assertEqual(tags["ai.operation.parentId"], reqid, "Exception parent id") - self.assertEqual(len(data["exceptions"]), 1, "Exception count") - exc = data["exceptions"][0] - self.assertEqual(exc["typeName"], "ValueError", "Exception type") - self.assertEqual(exc["hasFullStack"], True, "Has full stack") - self.assertEqual( - exc["parsedStack"][0]["method"], "thrower", "Stack frame method name" - ) - self.assertEqual(tags["ai.user.id"], "SLACKFROMID") - self.assertEqual(tags["ai.session.id"], "CONVERSATIONID") - self.assertEqual(data["properties"]["channelId"], "SLACK", "channelId=SLACK") - self.assertEqual( - data["properties"]["activityId"], "bf3cc9a2f5de...", "activityID is set" - ) - self.assertEqual( - data["properties"]["activityType"], "message", "activityType == message" - ) - - def test_error(self): - """Tests that Http404 exception does not generate an exception event - and the request telemetry item properly logs the failure""" - - response = self.invoke_post("errorer") - self.assertEqual(response.status_code, 404) - - event = self.get_events(1) - tags = event["tags"] - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(tags["ai.operation.name"], "POST /errorer", "Operation name") - self.assertEqual(data["responseCode"], 404, "Status code") - self.assertEqual(data["success"], False, "Success value") - self.assertEqual(data["url"], "http://testserver/errorer", "Request url") - - def test_template(self): - """Tests that views using templates operate correctly and that template data is logged""" - response = self.invoke_post("templater/ctx") - self.assertEqual(response.status_code, 200) - - event = self.get_events(1) - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(data["success"], True, "Success value") - self.assertEqual(data["responseCode"], 200, "Status code") - self.assertEqual( - data["properties"]["template_name"], "template.html", "Template name" - ) - - def test_no_view_arguments(self): - """Tests that view id logging is off by default""" - self.plug_sender() - # response = self.client.get('/getid/24') - response = self.invoke_post("getid/24") - self.assertEqual(response.status_code, 200) - - event = self.get_events(1) - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertTrue( - "properties" not in data or "view_arg_0" not in data["properties"] - ) - - def test_no_view(self): - """Tests that requests to URLs not backed by views are still logged""" - # response = self.client.get('/this/view/does/not/exist') - response = self.invoke_post("this/view/does/not/exist") - self.assertEqual(response.status_code, 404) - - event = self.get_events(1) - tags = event["tags"] - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(data["responseCode"], 404, "Status code") - self.assertEqual(data["success"], False, "Success value") - self.assertEqual(data["name"], "POST /this/view/does/not/exist", "Request name") - self.assertEqual( - data["url"], "http://testserver/this/view/does/not/exist", "Request url" - ) - - def test_401_success(self): - """Tests that a 401 status code is considered successful""" - # response = self.client.get("/returncode/401") - response = self.invoke_post("returncode/405") - self.assertEqual(response.status_code, 405) - - event = self.get_events(1) - tags = event["tags"] - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual( - tags["ai.operation.name"], "POST /returncode/405", "Operation name" - ) - self.assertEqual(data["responseCode"], 405, "Status code") - self.assertEqual(data["success"], False, "Success value") - self.assertEqual(data["url"], "http://testserver/returncode/405", "Request url") - - def invoke_post(self, first_level_directory: str): - client = RequestsClient() - return client.post( - f"http://localhost/{first_level_directory}", - json={ - "type": "message", - "id": "bf3cc9a2f5de...", - "timestamp": "2016-10-19T20:17:52.2891902Z", - "serviceUrl": "https://smba.trafficmanager.net/apis", - "channelId": "SLACK", - "from": {"id": "FROMID", "name": "user's name"}, - "conversation": {"id": "CONVERSATIONID", "name": "conversation's name"}, - "recipient": {"id": "RECIPIENTID", "name": "bot's name"}, - "text": "Haircut on Saturday", - }, - ) - - -@modify_settings( - **{ - MIDDLEWARE_NAME: { - "append": "botbuilder.applicationinsights.django.ApplicationInsightsMiddleware" - } - } -) -class RequestSettingsTests(AITestCase): - # This type needs to plug the sender during the test -- doing it in setUp would have nil effect - # because each method's override_settings wouldn't have happened by then. - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "use_view_name": True}) - def test_use_view_name(self): - """Tests that request names are set to view names when use_view_name=True""" - self.plug_sender() - self.client.get("/") - event = self.get_events(1) - self.assertEqual( - event["data"]["baseData"]["name"], "GET aitest.views.home", "Request name" - ) - self.assertEqual( - event["tags"]["ai.operation.name"], - "GET aitest.views.home", - "Operation name", - ) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "use_view_name": False}) - def test_use_view_name_off(self): - """Tests that request names are set to URLs when use_view_name=False""" - self.plug_sender() - self.client.get("/") - event = self.get_events(1) - self.assertEqual(event["data"]["baseData"]["name"], "GET /", "Request name") - self.assertEqual(event["tags"]["ai.operation.name"], "GET /", "Operation name") - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "use_view_name": True}) - def test_view_name_class(self): - """Tests that classes can be correctly identified when use_view_name=True""" - self.plug_sender() - self.client.get("/class") - event = self.get_events(1) - self.assertEqual( - event["data"]["baseData"]["name"], - "GET aitest.views.classview", - "Request name", - ) - self.assertEqual( - event["tags"]["ai.operation.name"], - "GET aitest.views.classview", - "Operation name", - ) - - @override_settings(APPLICATION_INSIGHTS=None) - def test_appinsights_still_supplied(self): - """Tests that appinsights is still added to requests even if APPLICATION_INSIGHTS is unspecified""" - # This uses request.appinsights -- it will crash if it's not there. - response = self.invoke_post("logger") - self.assertEqual(response.status_code, 200) - - @override_settings( - APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "record_view_arguments": True} - ) - def test_view_id(self): - """Tests that view arguments are logged when record_view_arguments=True""" - self.plug_sender() - response = self.invoke_post("getid/24") - self.assertEqual(response.status_code, 200) - - event = self.get_events(1) - props = event["data"]["baseData"]["properties"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(props["view_arg_0"], "24", "View argument") - - @override_settings( - APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "log_exceptions": False} - ) - def test_log_exceptions_off(self): - """Tests that exceptions are not logged when log_exceptions=False""" - self.plug_sender() - response = self.invoke_post("thrower") - self.assertEqual(response.status_code, 500) - - event = self.get_events(1) - data = event["data"]["baseData"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Request", "Event type" - ) - self.assertEqual(data["responseCode"], 500, "Response code") - - def invoke_post(self, first_level_directory: str): - client = RequestsClient() - return client.post( - f"http://localhost/{first_level_directory}", - json={ - "type": "message", - "id": "bf3cc9a2f5de...", - "timestamp": "2016-10-19T20:17:52.2891902Z", - "serviceUrl": "https://smba.trafficmanager.net/apis", - "channelId": "SLACK", - "from": {"id": "FROMID", "name": "user's name"}, - "conversation": {"id": "CONVERSATIONID", "name": "conversation's name"}, - "recipient": {"id": "RECIPIENTID", "name": "bot's name"}, - "text": "Haircut on Saturday", - }, - ) - - -class SettingsTests(TestCase): - def setUp(self): - # Just clear out any cached objects - common.saved_clients = {} - common.saved_channels = {} - - def test_no_app_insights(self): - """Tests that events are swallowed when APPLICATION_INSIGHTS is unspecified""" - client = common.create_client() - self.assertTrue(type(client.channel.sender) is NullSender) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY}) - def test_default_endpoint(self): - """Tests that the default endpoint is used when endpoint is unspecified""" - client = common.create_client() - self.assertEqual(client.channel.sender.service_endpoint_uri, DEFAULT_ENDPOINT) - - @override_settings( - APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "endpoint": TEST_ENDPOINT} - ) - def test_overridden_endpoint(self): - """Tests that the endpoint is used when specified""" - client = common.create_client() - self.assertEqual(client.channel.sender.service_endpoint_uri, TEST_ENDPOINT) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "send_time": 999}) - def test_send_time(self): - """Tests that send_time is propagated to sender""" - client = common.create_client() - self.assertEqual(client.channel.sender.send_time, 999) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY, "send_interval": 999}) - def test_send_interval(self): - """Tests that send_interval is propagated to sender""" - client = common.create_client() - self.assertEqual(client.channel.sender.send_interval, 999) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY}) - def test_default_send_time(self): - """Tests that send_time is equal to the default when it is unspecified""" - client = common.create_client() - self.assertEqual( - client.channel.sender.send_time, AsynchronousSender().send_time - ) - - @override_settings(APPLICATION_INSIGHTS={"ikey": TEST_IKEY}) - def test_default_send_interval(self): - """Tests that send_interval is equal to the default when it is unspecified""" - client = common.create_client() - self.assertEqual( - client.channel.sender.send_interval, AsynchronousSender().send_interval - ) - - -@override_settings( - LOGGING={ - "version": 1, - "handlers": { - "appinsights": { - "class": "botbuilder.applicationinsights.django.LoggingHandler", - "level": "INFO", - } - }, - "loggers": {__name__: {"handlers": ["appinsights"], "level": "INFO"}}, - }, - APPLICATION_INSIGHTS={"ikey": TEST_IKEY}, -) -class LoggerTests(AITestCase): - def setUp(self): - self.plug_sender() - - def test_log_error(self): - """Tests an error trace telemetry is properly sent""" - django.setup() - logger = logging.getLogger(__name__) - msg = "An error log message" - logger.error(msg) - - event = self.get_events(1) - data = event["data"]["baseData"] - props = data["properties"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Message", "Event type" - ) - self.assertEqual(event["iKey"], TEST_IKEY) - self.assertEqual(data["message"], msg, "Log message") - self.assertEqual(data["severityLevel"], 3, "Severity level") - self.assertEqual(props["fileName"], "tests.py", "Filename property") - self.assertEqual(props["level"], "ERROR", "Level property") - self.assertEqual(props["module"], "tests", "Module property") - - def test_log_info(self): - """Tests an info trace telemetry is properly sent""" - django.setup() - logger = logging.getLogger(__name__) - msg = "An info message" - logger.info(msg) - - event = self.get_events(1) - data = event["data"]["baseData"] - props = data["properties"] - self.assertEqual( - event["name"], "Microsoft.ApplicationInsights.Message", "Event type" - ) - self.assertEqual(event["iKey"], TEST_IKEY) - self.assertEqual(data["message"], msg, "Log message") - self.assertEqual(data["severityLevel"], 1, "Severity level") - self.assertEqual(props["fileName"], "tests.py", "Filename property") - self.assertEqual(props["level"], "INFO", "Level property") - self.assertEqual(props["module"], "tests", "Module property") - - -class MockSender(SenderBase): - def __init__(self): - SenderBase.__init__(self, DEFAULT_ENDPOINT) - self.events = [] - - def send(self, data): - self.events.extend(a.write() for a in data) diff --git a/libraries/botbuilder-applicationinsights/django_tests/urls.py b/libraries/botbuilder-applicationinsights/django_tests/urls.py deleted file mode 100644 index f544461c1..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/urls.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from django.conf.urls import include, url -from django.contrib import admin - -from . import views - -urlpatterns = [ - url(r"^$", views.home, name="home"), - url(r"^logger$", views.logger, name="logger"), - url(r"^botlog_event$", views.botlog_event, name="botlog_event"), - url(r"^thrower$", views.thrower, name="thrower"), - url(r"^errorer$", views.errorer, name="errorer"), - url(r"^getid/([0-9]+)$", views.getid, name="getid"), - url(r"^returncode/([0-9]+)$", views.returncode, name="returncode"), - url(r"^templater/([^/]*)$", views.templater, name="templater"), - url(r"^class$", views.classview(), name="class"), -] diff --git a/libraries/botbuilder-applicationinsights/django_tests/views.py b/libraries/botbuilder-applicationinsights/django_tests/views.py deleted file mode 100644 index 43653cef7..000000000 --- a/libraries/botbuilder-applicationinsights/django_tests/views.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import html -from rest_framework.decorators import api_view -from botbuilder.applicationinsights.django import common -from botbuilder.applicationinsights import ApplicationInsightsTelemetryClient -from django.http import HttpResponse, Http404 -from django.template.response import TemplateResponse - - -@api_view(["POST"]) -def home(request): - # Basic request, no logging. Check BOT properties added. - return HttpResponse("Welcome home") - - -@api_view(["POST"]) -def botlog_event(request): - # Simulates a bot. - telemetry = ApplicationInsightsTelemetryClient( - None, common.create_client() - ) # Used shared client AppInsights uses. - telemetry.track_event("botevent", {"foo": "bar", "moo": "cow"}) - return HttpResponse("We logged a bot event") - - -@api_view(["POST"]) -def logger(request): - # Log with Application Insights - request.appinsights.client.track_trace("Logger message", {"property": "value"}) - return HttpResponse("We logged a message") - - -@api_view(["POST"]) -def thrower(request): - raise ValueError("This is an unexpected exception") - - -@api_view(["POST"]) -def errorer(request): - raise Http404("This is a 404 error") - - -def echoer(request): - return HttpResponse(html.escape(request.appinsights.request.id)) - - -@api_view(["POST"]) -def getid(request, id): - return HttpResponse(str(id)) - - -@api_view(["POST"]) -def returncode(request, id): - return HttpResponse("returncode", status=int(html.escape(id))) - - -@api_view(["POST"]) -def templater(request, data): - return TemplateResponse(request, "template.html", {"context": html.escape(data)}) - - -class classview: - def __call__(self, request): - return HttpResponse("You called a class.") From afbecb6d7fe81f377ffc7ecffa6eb7b68a4ddb06 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 26 Mar 2024 13:51:34 -0500 Subject: [PATCH 031/102] Corrected Gov LoginUrl prefix (#2091) Co-authored-by: Tracy Boehrer --- .../botframework/connector/auth/government_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index 6574859eb..dd235aba7 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -24,7 +24,7 @@ class GovernmentConstants(ABC): """ TO CHANNEL FROM BOT: Login URL prefix """ - TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX = "https://login.microsoftonline.com/" + TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX = "https://login.microsoftonline.us/" DEFAULT_CHANNEL_AUTH_TENANT = "MicrosoftServices.onmicrosoft.us" From 24025b5ee218b87c91c96169d3add59e48b9f31f Mon Sep 17 00:00:00 2001 From: Alex Acebo Date: Mon, 1 Apr 2024 14:00:43 -0700 Subject: [PATCH 032/102] add properties to ChannelAccount model --- libraries/botbuilder-schema/botbuilder/schema/_models_py3.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 523204c0e..0b7d285d2 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -1280,6 +1280,7 @@ class ChannelAccount(Model): "name": {"key": "name", "type": "str"}, "aad_object_id": {"key": "aadObjectId", "type": "str"}, "role": {"key": "role", "type": "str"}, + "properties": {"key": "properties", "type": "object"} } def __init__( @@ -1289,6 +1290,7 @@ def __init__( name: str = None, aad_object_id: str = None, role=None, + properties=None, **kwargs ) -> None: super(ChannelAccount, self).__init__(**kwargs) @@ -1296,6 +1298,7 @@ def __init__( self.name = name self.aad_object_id = aad_object_id self.role = role + self.properties = properties class ConversationAccount(Model): From 45fd8a43a8b1e8516e855fceb6350f287cbc25e3 Mon Sep 17 00:00:00 2001 From: Alex Acebo Date: Mon, 1 Apr 2024 14:00:52 -0700 Subject: [PATCH 033/102] Revert "add properties to ChannelAccount model" This reverts commit 24025b5ee218b87c91c96169d3add59e48b9f31f. --- libraries/botbuilder-schema/botbuilder/schema/_models_py3.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 0b7d285d2..523204c0e 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -1280,7 +1280,6 @@ class ChannelAccount(Model): "name": {"key": "name", "type": "str"}, "aad_object_id": {"key": "aadObjectId", "type": "str"}, "role": {"key": "role", "type": "str"}, - "properties": {"key": "properties", "type": "object"} } def __init__( @@ -1290,7 +1289,6 @@ def __init__( name: str = None, aad_object_id: str = None, role=None, - properties=None, **kwargs ) -> None: super(ChannelAccount, self).__init__(**kwargs) @@ -1298,7 +1296,6 @@ def __init__( self.name = name self.aad_object_id = aad_object_id self.role = role - self.properties = properties class ConversationAccount(Model): From 864fb90ac8ea8688380fe27b5951ca0b09dbd237 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Apr 2024 16:17:28 -0500 Subject: [PATCH 034/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 8d55cd230..536d12031 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -89,10 +89,10 @@ jobs: testResultsFiles: '**/test-results.$(PYTHON_VERSION).xml' testRunTitle: 'Python $(PYTHON_VERSION)' - - script: 'COVERALLS_REPO_TOKEN=$(PythonCoverallsToken) coveralls' - displayName: 'Push test results to coveralls https://coveralls.io/github/microsoft/botbuilder-python' - continueOnError: true - condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'false')) + #- script: 'COVERALLS_REPO_TOKEN=$(PythonCoverallsToken) coveralls' + # displayName: 'Push test results to coveralls https://coveralls.io/github/microsoft/botbuilder-python' + # continueOnError: true + # condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'false')) - powershell: | Set-Location .. From eb4901a5cb5e19a36ce60e1283753bdf0f158a84 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Apr 2024 16:26:30 -0500 Subject: [PATCH 035/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 536d12031..8d55cd230 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -89,10 +89,10 @@ jobs: testResultsFiles: '**/test-results.$(PYTHON_VERSION).xml' testRunTitle: 'Python $(PYTHON_VERSION)' - #- script: 'COVERALLS_REPO_TOKEN=$(PythonCoverallsToken) coveralls' - # displayName: 'Push test results to coveralls https://coveralls.io/github/microsoft/botbuilder-python' - # continueOnError: true - # condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'false')) + - script: 'COVERALLS_REPO_TOKEN=$(PythonCoverallsToken) coveralls' + displayName: 'Push test results to coveralls https://coveralls.io/github/microsoft/botbuilder-python' + continueOnError: true + condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'false')) - powershell: | Set-Location .. From 9b6f0e69a30179a5c3280d2749517eab7abab40e Mon Sep 17 00:00:00 2001 From: Alex Acebo Date: Mon, 1 Apr 2024 17:43:49 -0400 Subject: [PATCH 036/102] Update _models_py3.py (#2092) Co-authored-by: tracyboehrer --- libraries/botbuilder-schema/botbuilder/schema/_models_py3.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 523204c0e..e9eb1529d 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -1280,6 +1280,7 @@ class ChannelAccount(Model): "name": {"key": "name", "type": "str"}, "aad_object_id": {"key": "aadObjectId", "type": "str"}, "role": {"key": "role", "type": "str"}, + "properties": {"key": "properties", "type": "object"}, } def __init__( @@ -1289,6 +1290,7 @@ def __init__( name: str = None, aad_object_id: str = None, role=None, + properties=None, **kwargs ) -> None: super(ChannelAccount, self).__init__(**kwargs) @@ -1296,6 +1298,7 @@ def __init__( self.name = name self.aad_object_id = aad_object_id self.role = role + self.properties = properties class ConversationAccount(Model): From 48ba78e15e8a287fa2d87002cc4e9cb4a0c0a638 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 17 Apr 2024 09:49:32 -0500 Subject: [PATCH 037/102] OAuthScope parity with JS and DotNet (#2093) * OAuthScope parity with JS and DotNet * black formatting --------- Co-authored-by: Tracy Boehrer --- .../auth/authentication_constants.py | 2 +- .../connector/auth/government_constants.py | 2 +- .../auth/microsoft_app_credentials.py | 20 +++++++------------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py index f1a24de08..90cb5656f 100644 --- a/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/authentication_constants.py @@ -22,7 +22,7 @@ class AuthenticationConstants(ABC): DEFAULT_CHANNEL_AUTH_TENANT = "botframework.com" # TO CHANNEL FROM BOT: OAuth scope to request - TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.com/.default" + TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.com" # TO BOT FROM CHANNEL: Token issuer TO_BOT_FROM_CHANNEL_TOKEN_ISSUER = "https://api.botframework.com" diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index dd235aba7..aba16e396 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -31,7 +31,7 @@ class GovernmentConstants(ABC): """ TO CHANNEL FROM BOT: OAuth scope to request """ - TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.us/.default" + TO_CHANNEL_FROM_BOT_OAUTH_SCOPE = "https://api.botframework.us" """ TO BOT FROM CHANNEL: Token issuer 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 24c230007..532071667 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -30,13 +30,6 @@ def __init__( self.microsoft_app_password = password self.app = None - # This check likely needs to be more nuanced than this. Assuming - # "/.default" precludes other valid suffixes - scope = self.oauth_scope - if oauth_scope and not scope.endswith("/.default"): - scope += "/.default" - self.scopes = [scope] - @staticmethod def empty(): return MicrosoftAppCredentials("", "") @@ -47,17 +40,18 @@ def get_access_token(self, force_refresh: bool = False) -> str: :return: The access token for the given app id and password. """ + scope = self.oauth_scope + if not scope.endswith("/.default"): + scope += "/.default" + scopes = [scope] + # Firstly, looks up a token from cache # Since we are looking for token for the current app, NOT for an end user, # notice we give account parameter as None. - auth_token = self.__get_msal_app().acquire_token_silent( - self.scopes, account=None - ) + auth_token = self.__get_msal_app().acquire_token_silent(scopes, account=None) if not auth_token: # No suitable token exists in cache. Let's get a new one from AAD. - auth_token = self.__get_msal_app().acquire_token_for_client( - scopes=self.scopes - ) + auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) return auth_token["access_token"] def __get_msal_app(self): From 2407c51698a7ab3a81851e2eeafb4624c2c06cc9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 17 Apr 2024 09:59:05 -0500 Subject: [PATCH 038/102] Updated generators to use CloudAdapter and latest version (#2094) * Updated generators to use CloudAdapter and latest version * Updated version and added missing SingleTenant config items --------- Co-authored-by: Tracy Boehrer --- .../adapter_with_error_handler.py | 7 +++---- .../core/{{cookiecutter.bot_name}}/app.py | 14 +++++++------- .../core/{{cookiecutter.bot_name}}/config.py | 2 ++ .../{{cookiecutter.bot_name}}/requirements.txt | 4 ++-- .../echo/{{cookiecutter.bot_name}}/app.py | 18 +++++++----------- .../echo/{{cookiecutter.bot_name}}/config.py | 2 ++ .../empty/{{cookiecutter.bot_name}}/app.py | 18 +++++++----------- .../empty/{{cookiecutter.bot_name}}/config.py | 2 ++ 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/adapter_with_error_handler.py b/generators/app/templates/core/{{cookiecutter.bot_name}}/adapter_with_error_handler.py index af1f81d98..7959968ce 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/adapter_with_error_handler.py +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/adapter_with_error_handler.py @@ -5,18 +5,17 @@ from datetime import datetime from botbuilder.core import ( - BotFrameworkAdapter, - BotFrameworkAdapterSettings, ConversationState, TurnContext, ) +from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication from botbuilder.schema import ActivityTypes, Activity -class AdapterWithErrorHandler(BotFrameworkAdapter): +class AdapterWithErrorHandler(CloudAdapter): def __init__( self, - settings: BotFrameworkAdapterSettings, + settings: ConfigurationBotFrameworkAuthentication, conversation_state: ConversationState, ): super().__init__(settings) diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/app.py b/generators/app/templates/core/{{cookiecutter.bot_name}}/app.py index 8d2e9fc5d..c19ea27e2 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/app.py +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/app.py @@ -8,16 +8,16 @@ - Handle user interruptions for such things as `Help` or `Cancel`. - Prompt for and validate requests for information from the user. """ - +from http import HTTPStatus from aiohttp import web from aiohttp.web import Request, Response, json_response from botbuilder.core import ( - BotFrameworkAdapterSettings, ConversationState, MemoryStorage, UserState, ) from botbuilder.core.integration import aiohttp_error_middleware +from botbuilder.integration.aiohttp import ConfigurationBotFrameworkAuthentication from botbuilder.schema import Activity from config import DefaultConfig @@ -31,7 +31,7 @@ # Create adapter. # See https://aka.ms/about-bot-adapter to learn more about how bots work. -SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) +SETTINGS = ConfigurationBotFrameworkAuthentication(CONFIG) # Create MemoryStorage, UserState and ConversationState MEMORY = MemoryStorage() @@ -49,21 +49,21 @@ BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG) -# Listen for incoming requests on /api/messages. +# Listen for incoming requests on /api/messages async def messages(req: Request) -> Response: # Main bot message handler. if "application/json" in req.headers["Content-Type"]: body = await req.json() else: - return Response(status=415) + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) activity = Activity().deserialize(body) auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" - response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + response = await ADAPTER.process_activity(auth_header, activity, BOT.on_turn) if response: return json_response(data=response.body, status=response.status) - return Response(status=201) + return Response(status=HTTPStatus.OK) APP = web.Application(middlewares=[aiohttp_error_middleware]) diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/config.py b/generators/app/templates/core/{{cookiecutter.bot_name}}/config.py index 46973c345..f2d31d7ad 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/config.py +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/config.py @@ -10,6 +10,8 @@ class DefaultConfig: PORT = 3978 APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") + APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") LUIS_APP_ID = os.environ.get("LuisAppId", "") LUIS_API_KEY = os.environ.get("LuisAPIKey", "") # LUIS endpoint host name, ie "westus.api.cognitive.microsoft.com" diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/requirements.txt b/generators/app/templates/core/{{cookiecutter.bot_name}}/requirements.txt index 26674db81..0c93ce564 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/requirements.txt +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/requirements.txt @@ -1,4 +1,4 @@ -botbuilder-integration-aiohttp>=4.15.0 +botbuilder-integration-aiohttp>=4.14.8 botbuilder-dialogs>=4.15.0 -botbuilder-ai>=4.15.0 +botbuilder-ai>=4.14.8 datatypes-date-time>=1.0.0.a2 diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/app.py b/generators/app/templates/echo/{{cookiecutter.bot_name}}/app.py index 6a21648d0..8dc9fbecb 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/app.py +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/app.py @@ -5,14 +5,12 @@ import traceback from datetime import datetime +from http import HTTPStatus from aiohttp import web from aiohttp.web import Request, Response, json_response -from botbuilder.core import ( - BotFrameworkAdapterSettings, - TurnContext, - BotFrameworkAdapter, -) +from botbuilder.core import TurnContext from botbuilder.core.integration import aiohttp_error_middleware +from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication from botbuilder.schema import Activity, ActivityTypes from bot import MyBot @@ -22,9 +20,7 @@ # Create adapter. # See https://aka.ms/about-bot-adapter to learn more about how bots work. -SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) -ADAPTER = BotFrameworkAdapter(SETTINGS) - +ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) # Catch-all for errors. async def on_error(context: TurnContext, error: Exception): @@ -66,15 +62,15 @@ async def messages(req: Request) -> Response: if "application/json" in req.headers["Content-Type"]: body = await req.json() else: - return Response(status=415) + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) activity = Activity().deserialize(body) auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" - response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + response = await ADAPTER.process_activity(auth_header, activity, BOT.on_turn) if response: return json_response(data=response.body, status=response.status) - return Response(status=201) + return Response(status=HTTPStatus.OK) APP = web.Application(middlewares=[aiohttp_error_middleware]) diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/config.py b/generators/app/templates/echo/{{cookiecutter.bot_name}}/config.py index 51e0988ed..aad3896f4 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/config.py +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/config.py @@ -10,3 +10,5 @@ class DefaultConfig: PORT = 3978 APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") + APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/app.py b/generators/app/templates/empty/{{cookiecutter.bot_name}}/app.py index a6aa1bcd1..29f91ab47 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/app.py +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/app.py @@ -5,14 +5,12 @@ import traceback from datetime import datetime +from http import HTTPStatus from aiohttp import web from aiohttp.web import Request, Response, json_response -from botbuilder.core import ( - BotFrameworkAdapterSettings, - TurnContext, - BotFrameworkAdapter, -) +from botbuilder.core import TurnContext from botbuilder.core.integration import aiohttp_error_middleware +from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication from botbuilder.schema import Activity, ActivityTypes from bot import MyBot @@ -22,9 +20,7 @@ # Create adapter. # See https://aka.ms/about-bot-adapter to learn more about how bots work. -SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) -ADAPTER = BotFrameworkAdapter(SETTINGS) - +ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) # Catch-all for errors. async def on_error(context: TurnContext, error: Exception): @@ -66,15 +62,15 @@ async def messages(req: Request) -> Response: if "application/json" in req.headers["Content-Type"]: body = await req.json() else: - return Response(status=415) + return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE) activity = Activity().deserialize(body) auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" - response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + response = await ADAPTER.process_activity(auth_header, activity, BOT.on_turn) if response: return json_response(data=response.body, status=response.status) - return Response(status=201) + return Response(status=HTTPStatus.OK) APP = web.Application(middlewares=[aiohttp_error_middleware]) diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/config.py b/generators/app/templates/empty/{{cookiecutter.bot_name}}/config.py index 51e0988ed..aad3896f4 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/config.py +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/config.py @@ -10,3 +10,5 @@ class DefaultConfig: PORT = 3978 APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") + APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") From 14753f620953550ae5cf852590ee7becbce7e12e Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 17 Apr 2024 13:57:00 -0500 Subject: [PATCH 039/102] Bumped next version to 4.16.0 (#2096) Co-authored-by: Tracy Boehrer --- README.md | 2 +- .../botbuilder/adapters/slack/about.py | 2 +- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-adapters-slack/setup.py | 6 +++--- libraries/botbuilder-ai/botbuilder/ai/about.py | 2 +- libraries/botbuilder-ai/requirements.txt | 4 ++-- libraries/botbuilder-ai/setup.py | 4 ++-- .../botbuilder/applicationinsights/about.py | 2 +- libraries/botbuilder-applicationinsights/requirements.txt | 2 +- libraries/botbuilder-applicationinsights/setup.py | 6 +++--- libraries/botbuilder-azure/botbuilder/azure/about.py | 2 +- libraries/botbuilder-azure/setup.py | 4 ++-- libraries/botbuilder-core/botbuilder/core/about.py | 2 +- libraries/botbuilder-core/requirements.txt | 6 +++--- libraries/botbuilder-core/setup.py | 8 ++++---- libraries/botbuilder-dialogs/botbuilder/dialogs/about.py | 2 +- libraries/botbuilder-dialogs/requirements.txt | 6 +++--- libraries/botbuilder-dialogs/setup.py | 6 +++--- .../botbuilder/integration/aiohttp/about.py | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 4 ++-- libraries/botbuilder-integration-aiohttp/setup.py | 8 ++++---- .../integration/applicationinsights/aiohttp/about.py | 2 +- .../setup.py | 8 ++++---- libraries/botbuilder-schema/setup.py | 2 +- libraries/botbuilder-testing/botbuilder/testing/about.py | 2 +- libraries/botbuilder-testing/requirements.txt | 6 +++--- libraries/botbuilder-testing/setup.py | 8 ++++---- .../botframework/connector/about.py | 2 +- libraries/botframework-connector/requirements.txt | 2 +- libraries/botframework-connector/setup.py | 4 ++-- .../botframework/streaming/about.py | 2 +- libraries/botframework-streaming/requirements.txt | 4 ++-- libraries/botframework-streaming/setup.py | 6 +++--- 33 files changed, 65 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index df939dd52..39b134ac1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | Code Style | |----|---------------|--------------|-----------------|--| -| Main | 4.15.0 Builds | [![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/Python-CI-PR-yaml?branchName=main)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=771&branchName=main) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-python/badge.svg?branch=HEAD)](https://coveralls.io/github/microsoft/botbuilder-python?branch=HEAD) | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | +| Main | 4.16.0 Builds | [![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/Python-CI-PR-yaml?branchName=main)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=771&branchName=main) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-python/badge.svg?branch=HEAD)](https://coveralls.io/github/microsoft/botbuilder-python?branch=HEAD) | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | ## Packages diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py index a7f0c2b88..2da3fe8e7 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-adapters-slack" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 312d38e8a..aad8e0cae 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ aiohttp==3.9.3 pyslack -botbuilder-core==4.15.0 +botbuilder-core==4.16.0 slackclient diff --git a/libraries/botbuilder-adapters-slack/setup.py b/libraries/botbuilder-adapters-slack/setup.py index 41ac5dc42..d3eb8cbd1 100644 --- a/libraries/botbuilder-adapters-slack/setup.py +++ b/libraries/botbuilder-adapters-slack/setup.py @@ -5,9 +5,9 @@ from setuptools import setup REQUIRES = [ - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botbuilder-core==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botbuilder-core==4.16.0", "pyslack", "slackclient", ] diff --git a/libraries/botbuilder-ai/botbuilder/ai/about.py b/libraries/botbuilder-ai/botbuilder/ai/about.py index c6bda926b..22d69d517 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/about.py +++ b/libraries/botbuilder-ai/botbuilder/ai/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-ai" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-ai/requirements.txt b/libraries/botbuilder-ai/requirements.txt index 221374746..876a8d459 100644 --- a/libraries/botbuilder-ai/requirements.txt +++ b/libraries/botbuilder-ai/requirements.txt @@ -1,6 +1,6 @@ msrest== 0.7.* -botbuilder-schema==4.15.0 -botbuilder-core==4.15.0 +botbuilder-schema==4.16.0 +botbuilder-core==4.16.0 requests==2.31.0 aiounittest==1.3.0 azure-cognitiveservices-language-luis==0.2.0 \ No newline at end of file diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 0b61efc81..989f1755c 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -6,8 +6,8 @@ REQUIRES = [ "azure-cognitiveservices-language-luis==0.2.0", - "botbuilder-schema==4.15.0", - "botbuilder-core==4.15.0", + "botbuilder-schema==4.16.0", + "botbuilder-core==4.16.0", "aiohttp==3.9.3", ] diff --git a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py index cf0b5e087..74f9edbb5 100644 --- a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py +++ b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-applicationinsights" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-applicationinsights/requirements.txt b/libraries/botbuilder-applicationinsights/requirements.txt index 7d22c3724..dd908fd7c 100644 --- a/libraries/botbuilder-applicationinsights/requirements.txt +++ b/libraries/botbuilder-applicationinsights/requirements.txt @@ -1,3 +1,3 @@ msrest== 0.7.* -botbuilder-core==4.15.0 +botbuilder-core==4.16.0 aiounittest==1.3.0 \ No newline at end of file diff --git a/libraries/botbuilder-applicationinsights/setup.py b/libraries/botbuilder-applicationinsights/setup.py index a91a9acb1..8b9dc8107 100644 --- a/libraries/botbuilder-applicationinsights/setup.py +++ b/libraries/botbuilder-applicationinsights/setup.py @@ -6,9 +6,9 @@ REQUIRES = [ "applicationinsights==0.11.9", - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botbuilder-core==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botbuilder-core==4.16.0", ] TESTS_REQUIRES = [ "aiounittest==1.3.0", diff --git a/libraries/botbuilder-azure/botbuilder/azure/about.py b/libraries/botbuilder-azure/botbuilder/azure/about.py index 142cc9770..73d05308a 100644 --- a/libraries/botbuilder-azure/botbuilder/azure/about.py +++ b/libraries/botbuilder-azure/botbuilder/azure/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-azure" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-azure/setup.py b/libraries/botbuilder-azure/setup.py index 0b9806867..8d698a34f 100644 --- a/libraries/botbuilder-azure/setup.py +++ b/libraries/botbuilder-azure/setup.py @@ -8,8 +8,8 @@ "azure-cosmos==3.2.0", "azure-storage-blob==12.7.0", "azure-storage-queue==12.1.5", - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", "jsonpickle>=1.2,<1.5", ] TEST_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botbuilder-core/botbuilder/core/about.py b/libraries/botbuilder-core/botbuilder/core/about.py index 77319082e..b684dfef7 100644 --- a/libraries/botbuilder-core/botbuilder/core/about.py +++ b/libraries/botbuilder-core/botbuilder/core/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-core" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 8da13ad95..b33ed42bf 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -1,7 +1,7 @@ msrest== 0.7.* -botframework-connector==4.15.0 -botbuilder-schema==4.15.0 -botframework-streaming==4.15.0 +botframework-connector==4.16.0 +botbuilder-schema==4.16.0 +botframework-streaming==4.16.0 requests==2.31.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botbuilder-core/setup.py b/libraries/botbuilder-core/setup.py index a9b9b9fd0..6b4ec5929 100644 --- a/libraries/botbuilder-core/setup.py +++ b/libraries/botbuilder-core/setup.py @@ -4,11 +4,11 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" REQUIRES = [ - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botframework-streaming==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botframework-streaming==4.16.0", "jsonpickle>=1.2,<1.5", ] diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py index 609492f20..ac3c0bd7f 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-dialogs" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 05ccb8570..907c7fca7 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -1,7 +1,7 @@ msrest== 0.7.* -botframework-connector==4.15.0 -botbuilder-schema==4.15.0 -botbuilder-core==4.15.0 +botframework-connector==4.16.0 +botbuilder-schema==4.16.0 +botbuilder-core==4.16.0 requests==2.31.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botbuilder-dialogs/setup.py b/libraries/botbuilder-dialogs/setup.py index 574f8bbe7..8b2a44a27 100644 --- a/libraries/botbuilder-dialogs/setup.py +++ b/libraries/botbuilder-dialogs/setup.py @@ -13,9 +13,9 @@ "recognizers-text>=1.0.2a1", "recognizers-text-choice>=1.0.2a1", "babel==2.9.1", - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botbuilder-core==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botbuilder-core==4.16.0", ] TEST_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py index 898458512..fd45f9491 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-integration-aiohttp" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 85b38ee6f..e05b8da2f 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* -botframework-connector==4.15.0 -botbuilder-schema==4.15.0 +botframework-connector==4.16.0 +botbuilder-schema==4.16.0 aiohttp==3.9.3 diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index b69be5e90..fd743ecf3 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -4,11 +4,11 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" REQUIRES = [ - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botbuilder-core==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botbuilder-core==4.16.0", "yarl>=1.8.1", "aiohttp==3.9.3", ] diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py index 6a821af3e..ab88a6efe 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-integration-applicationinsights-aiohttp" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index f4b9933b8..185324083 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -7,10 +7,10 @@ REQUIRES = [ "applicationinsights>=0.11.9", "aiohttp==3.9.3", - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", - "botbuilder-core==4.15.0", - "botbuilder-applicationinsights==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", + "botbuilder-core==4.16.0", + "botbuilder-applicationinsights==4.16.0", ] TESTS_REQUIRES = [ "aiounittest==1.3.0", diff --git a/libraries/botbuilder-schema/setup.py b/libraries/botbuilder-schema/setup.py index ac285ff01..dba58aa60 100644 --- a/libraries/botbuilder-schema/setup.py +++ b/libraries/botbuilder-schema/setup.py @@ -5,7 +5,7 @@ from setuptools import setup NAME = "botbuilder-schema" -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" REQUIRES = ["msrest== 0.7.*", "urllib3<2.0.0"] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-testing/botbuilder/testing/about.py b/libraries/botbuilder-testing/botbuilder/testing/about.py index 5c866c335..89af35e45 100644 --- a/libraries/botbuilder-testing/botbuilder/testing/about.py +++ b/libraries/botbuilder-testing/botbuilder/testing/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-testing" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-testing/requirements.txt b/libraries/botbuilder-testing/requirements.txt index 8f011997c..b19d84f12 100644 --- a/libraries/botbuilder-testing/requirements.txt +++ b/libraries/botbuilder-testing/requirements.txt @@ -1,4 +1,4 @@ -botbuilder-schema==4.15.0 -botbuilder-core==4.15.0 -botbuilder-dialogs==4.15.0 +botbuilder-schema==4.16.0 +botbuilder-core==4.16.0 +botbuilder-dialogs==4.16.0 aiounittest==1.4.0 diff --git a/libraries/botbuilder-testing/setup.py b/libraries/botbuilder-testing/setup.py index 0dbc2304d..e7dc81fd7 100644 --- a/libraries/botbuilder-testing/setup.py +++ b/libraries/botbuilder-testing/setup.py @@ -5,10 +5,10 @@ from setuptools import setup REQUIRES = [ - "botbuilder-schema==4.15.0", - "botbuilder-core==4.15.0", - "botbuilder-dialogs==4.15.0", - "botbuilder-azure==4.15.0", + "botbuilder-schema==4.16.0", + "botbuilder-core==4.16.0", + "botbuilder-dialogs==4.16.0", + "botbuilder-azure==4.16.0", "pytest~=7.3.1", ] diff --git a/libraries/botframework-connector/botframework/connector/about.py b/libraries/botframework-connector/botframework/connector/about.py index 1df0ab2e9..9f11a65e3 100644 --- a/libraries/botframework-connector/botframework/connector/about.py +++ b/libraries/botframework-connector/botframework/connector/about.py @@ -5,7 +5,7 @@ __title__ = "botframework-connector" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 25d38e349..94b433187 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -1,5 +1,5 @@ msrest==0.7.* -botbuilder-schema==4.15.0 +botbuilder-schema==4.16.0 requests==2.31.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botframework-connector/setup.py b/libraries/botframework-connector/setup.py index 2e6c8fe64..ca4b72025 100644 --- a/libraries/botframework-connector/setup.py +++ b/libraries/botframework-connector/setup.py @@ -5,12 +5,12 @@ from setuptools import setup NAME = "botframework-connector" -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" REQUIRES = [ "msrest==0.7.*", # "requests>=2.23.0,<2.26", "PyJWT>=2.4.0", - "botbuilder-schema==4.15.0", + "botbuilder-schema==4.16.0", "msal==1.*", ] diff --git a/libraries/botframework-streaming/botframework/streaming/about.py b/libraries/botframework-streaming/botframework/streaming/about.py index 78ae10a20..9e4ba1d54 100644 --- a/libraries/botframework-streaming/botframework/streaming/about.py +++ b/libraries/botframework-streaming/botframework/streaming/about.py @@ -5,7 +5,7 @@ __title__ = "botframework-streaming" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botframework-streaming/requirements.txt b/libraries/botframework-streaming/requirements.txt index c806f7e77..db9a49e2b 100644 --- a/libraries/botframework-streaming/requirements.txt +++ b/libraries/botframework-streaming/requirements.txt @@ -1,3 +1,3 @@ msrest==0.7.* -botframework-connector>=4.15.0 -botbuilder-schema>=4.15.0 \ No newline at end of file +botframework-connector>=4.16.0 +botbuilder-schema>=4.16.0 \ No newline at end of file diff --git a/libraries/botframework-streaming/setup.py b/libraries/botframework-streaming/setup.py index 9a5f06e20..0a45151fc 100644 --- a/libraries/botframework-streaming/setup.py +++ b/libraries/botframework-streaming/setup.py @@ -4,10 +4,10 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.15.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" REQUIRES = [ - "botbuilder-schema==4.15.0", - "botframework-connector==4.15.0", + "botbuilder-schema==4.16.0", + "botframework-connector==4.16.0", ] root = os.path.abspath(os.path.dirname(__file__)) From bdf4fb658acff147d6572f6198e32832e94ea00a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 08:20:33 -0500 Subject: [PATCH 040/102] Bump aiohttp from 3.9.3 to 3.9.4 in /libraries/botbuilder-adapters-slack (#2097) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.3 to 3.9.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.3...v3.9.4) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index aad8e0cae..86671f1e3 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.9.3 +aiohttp==3.9.4 pyslack botbuilder-core==4.16.0 slackclient From 8edb0d0273fde5e0ce26ebd3ed2e3c1af2b0c11c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 08:21:26 -0500 Subject: [PATCH 041/102] Bump aiohttp in /libraries/botbuilder-integration-aiohttp (#2098) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.3 to 3.9.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.3...v3.9.4) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index e05b8da2f..77ce15fc2 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.16.0 botbuilder-schema==4.16.0 -aiohttp==3.9.3 +aiohttp==3.9.4 From 10e4332b10190da6850bc1cc1036a236e08e3aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:09:47 -0500 Subject: [PATCH 042/102] Bump the pip group across 2 directories with 1 update (#2100) Bumps the pip group with 1 update in the /libraries/botbuilder-adapters-slack directory: [aiohttp](https://github.com/aio-libs/aiohttp). Bumps the pip group with 1 update in the /libraries/botbuilder-integration-aiohttp directory: [aiohttp](https://github.com/aio-libs/aiohttp). Updates `aiohttp` from 3.9.4 to 3.9.5 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.4...v3.9.5) Updates `aiohttp` from 3.9.4 to 3.9.5 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.4...v3.9.5) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 86671f1e3..7fd82d816 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.9.4 +aiohttp==3.9.5 pyslack botbuilder-core==4.16.0 slackclient diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 77ce15fc2..1a1057e11 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.16.0 botbuilder-schema==4.16.0 -aiohttp==3.9.4 +aiohttp==3.9.5 From 74ec755d5efeee100b28e6e31eb4d1e349e725f2 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 30 Apr 2024 15:47:21 -0500 Subject: [PATCH 043/102] Added CosmosDB warning: Use CosmosDbPartitionedStorage instead. (#2104) * Added CosmosDB warning * CosmosDBStorage black --------- Co-authored-by: Tracy Boehrer --- .../botbuilder-azure/botbuilder/azure/cosmosdb_storage.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py b/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py index b5e29e650..2e383666f 100644 --- a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py +++ b/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py @@ -7,6 +7,7 @@ from typing import Dict, List from threading import Semaphore import json +import warnings from jsonpickle.pickler import Pickler from jsonpickle.unpickler import Unpickler import azure.cosmos.cosmos_client as cosmos_client # pylint: disable=no-name-in-module,import-error @@ -107,6 +108,9 @@ def __init__( :param config: """ super(CosmosDbStorage, self).__init__() + warnings.warn( + "CosmosDbStorage is obsolete. Use CosmosDbPartitionedStorage instead." + ) self.config = config self.client = client or cosmos_client.CosmosClient( self.config.endpoint, {"masterKey": self.config.masterkey} From 84fc24c0cb0e3bad59879dcc1d54179fd0fe483a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 09:51:15 -0500 Subject: [PATCH 044/102] --- (#2108) updated-dependencies: - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-ai/requirements.txt | 2 +- libraries/botbuilder-core/requirements.txt | 2 +- libraries/botbuilder-dialogs/requirements.txt | 2 +- libraries/botframework-connector/requirements.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder-ai/requirements.txt b/libraries/botbuilder-ai/requirements.txt index 876a8d459..cea1e6ded 100644 --- a/libraries/botbuilder-ai/requirements.txt +++ b/libraries/botbuilder-ai/requirements.txt @@ -1,6 +1,6 @@ msrest== 0.7.* botbuilder-schema==4.16.0 botbuilder-core==4.16.0 -requests==2.31.0 +requests==2.32.0 aiounittest==1.3.0 azure-cognitiveservices-language-luis==0.2.0 \ No newline at end of file diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index b33ed42bf..d9a4bc2f1 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -2,7 +2,7 @@ msrest== 0.7.* botframework-connector==4.16.0 botbuilder-schema==4.16.0 botframework-streaming==4.16.0 -requests==2.31.0 +requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 aiounittest==1.3.0 \ No newline at end of file diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 907c7fca7..2ee28a093 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -2,7 +2,7 @@ msrest== 0.7.* botframework-connector==4.16.0 botbuilder-schema==4.16.0 botbuilder-core==4.16.0 -requests==2.31.0 +requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 aiounittest==1.3.0 diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 94b433187..a762e8813 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -1,6 +1,6 @@ msrest==0.7.* botbuilder-schema==4.16.0 -requests==2.31.0 +requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 msal==1.* From 4e4cf64866056751f8b37586df9e1d2c60d5a4dd Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 23 May 2024 11:10:10 -0500 Subject: [PATCH 045/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 8d55cd230..acb3ea2d5 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -60,7 +60,7 @@ jobs: pip install -r ./libraries/botbuilder-ai/tests/requirements.txt pip install coveralls pip install pylint==2.17 - pip install black==22.3.0 + pip install black==24.3.0 displayName: 'Install dependencies' - script: 'black --check libraries' From 8bc698f96113b8361445fe187fa711e1ab0a877b Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 23 May 2024 11:17:13 -0500 Subject: [PATCH 046/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index acb3ea2d5..7a58ff339 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -60,7 +60,7 @@ jobs: pip install -r ./libraries/botbuilder-ai/tests/requirements.txt pip install coveralls pip install pylint==2.17 - pip install black==24.3.0 + pip install black==24.4.3 displayName: 'Install dependencies' - script: 'black --check libraries' From 6a63f1669128fa7795e4f860464b9517985429b0 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 23 May 2024 11:20:41 -0500 Subject: [PATCH 047/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 7a58ff339..6460bb907 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -60,7 +60,7 @@ jobs: pip install -r ./libraries/botbuilder-ai/tests/requirements.txt pip install coveralls pip install pylint==2.17 - pip install black==24.4.3 + pip install black==24.4.2 displayName: 'Install dependencies' - script: 'black --check libraries' From 3521ce3a9361de6eb45c8e74553ecf001d130c41 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 23 May 2024 13:01:40 -0500 Subject: [PATCH 048/102] fix: Corrections for errors in Skill Dialogs (#2110) * Corrections for errors in Skill Dialogs * black formatting * black formatting (PS. VS Code Black Formatter does not do exactly same thing as pipeline) * Test correction for Skill Dialog fix --------- Co-authored-by: Tracy Boehrer --- .../botbuilder/adapters/slack/slack_helper.py | 8 +++++--- .../botbuilder/ai/luis/luis_recognizer.py | 6 +++--- .../botbuilder/ai/luis/luis_recognizer_v2.py | 16 ++++++++++------ .../botbuilder/ai/luis/luis_recognizer_v3.py | 8 +++++--- .../ai/qna/dialogs/qnamaker_dialog.py | 6 +++--- .../botbuilder/ai/qna/models/ranker_types.py | 1 - .../botbuilder/ai/qna/qnamaker.py | 6 +++--- .../botbuilder/ai/qna/utils/train_utils.py | 4 +++- .../botbuilder/core/bot_framework_adapter.py | 8 +++++--- .../botbuilder/core/cloud_adapter_base.py | 12 +++++++++++- .../core/inspection/inspection_middleware.py | 8 ++++---- .../core/skills/_skill_handler_impl.py | 18 +++++++++--------- .../core/telemetry_logger_middleware.py | 18 +++++++++--------- .../botbuilder/core/turn_context.py | 9 ++++++++- .../tests/skills/test_skill_handler.py | 6 +++--- .../tests/teams/test_teams_activity_handler.py | 6 +++--- .../botbuilder/dialogs/_user_token_access.py | 16 ++++++++++------ .../botbuilder/dialogs/dialog_context.py | 6 +++--- .../botbuilder/dialogs/dialog_extensions.py | 14 ++++++++------ .../botbuilder/dialogs/dialog_manager.py | 8 +++++--- .../dialogs/memory/dialog_state_manager.py | 6 +++--- .../botbuilder/dialogs/prompts/oauth_prompt.py | 6 +++--- .../botbuilder/dialogs/skills/skill_dialog.py | 12 ++++++------ .../tests/test_dialog_manager.py | 14 +++++--------- .../aiohttp/bot_framework_http_client.py | 15 ++++++++------- .../aiohttp/skills/aio_http_client_factory.py | 10 ++++------ .../tests/skills/test_skill_http_client.py | 1 - .../auth/certificate_app_credentials.py | 6 +++--- .../connector/auth/government_constants.py | 1 - .../tests/test_skill_validation.py | 12 ++++++------ 30 files changed, 148 insertions(+), 119 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py index 80d30d275..35f720c8d 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py @@ -128,9 +128,11 @@ def payload_to_activity(payload: SlackPayload) -> Activity: channel_id="slack", conversation=ConversationAccount(id=payload.channel["id"], properties={}), from_property=ChannelAccount( - id=payload.message.bot_id - if payload.message.bot_id - else payload.user["id"] + id=( + payload.message.bot_id + if payload.message.bot_id + else payload.user["id"] + ) ), recipient=ChannelAccount(), channel_data=payload, diff --git a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer.py b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer.py index a95ebae96..bf6e15bfe 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer.py +++ b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer.py @@ -236,9 +236,9 @@ def fill_luis_event_properties( # Use the LogPersonalInformation flag to toggle logging PII data, text is a common example if self.log_personal_information and turn_context.activity.text: - properties[ - LuisTelemetryConstants.question_property - ] = turn_context.activity.text + properties[LuisTelemetryConstants.question_property] = ( + turn_context.activity.text + ) # Additional Properties can override "stock" properties. if telemetry_properties is not None: diff --git a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v2.py b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v2.py index b58c9b40c..507b10774 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v2.py +++ b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v2.py @@ -53,9 +53,11 @@ async def recognizer_internal(self, turn_context: TurnContext): staging=self.luis_recognizer_options_v2.staging, spell_check=self.luis_recognizer_options_v2.spell_check, bing_spell_check_subscription_key=self.luis_recognizer_options_v2.bing_spell_check_subscription_key, - log=self.luis_recognizer_options_v2.log - if self.luis_recognizer_options_v2.log is not None - else True, + log=( + self.luis_recognizer_options_v2.log + if self.luis_recognizer_options_v2.log is not None + else True + ), ) recognizer_result: RecognizerResult = RecognizerResult( @@ -65,9 +67,11 @@ async def recognizer_internal(self, turn_context: TurnContext): entities=LuisUtil.extract_entities_and_metadata( luis_result.entities, luis_result.composite_entities, - self.luis_recognizer_options_v2.include_instance_data - if self.luis_recognizer_options_v2.include_instance_data is not None - else True, + ( + self.luis_recognizer_options_v2.include_instance_data + if self.luis_recognizer_options_v2.include_instance_data is not None + else True + ), ), ) diff --git a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v3.py b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v3.py index b487abfb5..4e373023e 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v3.py +++ b/libraries/botbuilder-ai/botbuilder/ai/luis/luis_recognizer_v3.py @@ -116,9 +116,11 @@ def _build_url(self): uri += "/slots/%s/predict" % (self.luis_recognizer_options_v3.slot) params = "?verbose=%s&show-all-intents=%s&log=%s" % ( - "true" - if self.luis_recognizer_options_v3.include_instance_data - else "false", + ( + "true" + if self.luis_recognizer_options_v3.include_instance_data + else "false" + ), "true" if self.luis_recognizer_options_v3.include_all_intents else "false", "true" if self.luis_recognizer_options_v3.log else "false", ) diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/dialogs/qnamaker_dialog.py b/libraries/botbuilder-ai/botbuilder/ai/qna/dialogs/qnamaker_dialog.py index f1b052207..4bcceebaa 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/dialogs/qnamaker_dialog.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/dialogs/qnamaker_dialog.py @@ -248,9 +248,9 @@ async def __call_generate_answer(self, step_context: WaterfallStepContext): dialog_options.options.context = QnARequestContext() # Storing the context info - step_context.values[ - QnAMakerDialog.PROPERTY_CURRENT_QUERY - ] = step_context.context.activity.text + step_context.values[QnAMakerDialog.PROPERTY_CURRENT_QUERY] = ( + step_context.context.activity.text + ) # -Check if previous context is present, if yes then put it with the query # -Check for id if query is present in reverse index. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/models/ranker_types.py b/libraries/botbuilder-ai/botbuilder/ai/qna/models/ranker_types.py index 55d6799aa..811d61623 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/models/ranker_types.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/models/ranker_types.py @@ -3,7 +3,6 @@ class RankerTypes: - """Default Ranker Behaviour. i.e. Ranking based on Questions and Answer.""" DEFAULT = "Default" diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py index e0bf9bae1..825e08e8e 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py @@ -182,9 +182,9 @@ async def fill_qna_event( properties: Dict[str, str] = dict() metrics: Dict[str, float] = dict() - properties[ - QnATelemetryConstants.knowledge_base_id_property - ] = self._endpoint.knowledge_base_id + properties[QnATelemetryConstants.knowledge_base_id_property] = ( + self._endpoint.knowledge_base_id + ) text: str = turn_context.activity.text user_name: str = turn_context.activity.from_property.name diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py index 31c1ee441..b47eca8f9 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py @@ -45,7 +45,9 @@ async def call_train(self, feedback_records: List[FeedbackRecord]): await self._query_train(feedback_records) async def _query_train(self, feedback_records: List[FeedbackRecord]): - url: str = f"{ self._endpoint.host }/knowledgebases/{ self._endpoint.knowledge_base_id }/train" + url: str = ( + f"{ self._endpoint.host }/knowledgebases/{ self._endpoint.knowledge_base_id }/train" + ) payload_body = TrainRequestBody(feedback_records=feedback_records) http_request_helper = HttpRequestUtils(self._http_client) diff --git a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py index 42df5d73a..601693fd3 100644 --- a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py @@ -387,9 +387,11 @@ async def create_conversation( name=ActivityEventNames.create_conversation, channel_id=channel_id, service_url=service_url, - id=resource_response.activity_id - if resource_response.activity_id - else str(uuid.uuid4()), + id=( + resource_response.activity_id + if resource_response.activity_id + else str(uuid.uuid4()) + ), conversation=ConversationAccount( id=resource_response.id, tenant_id=parameters.tenant_id, diff --git a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py index 7e996c90c..dac83b0ce 100644 --- a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py +++ b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py @@ -150,7 +150,9 @@ async def continue_conversation( # pylint: disable=arguments-differ self, reference: ConversationReference, callback: Callable, - bot_app_id: str, + bot_app_id: str = None, # pylint: disable=unused-argument + claims_identity: ClaimsIdentity = None, # pylint: disable=unused-argument + audience: str = None, # pylint: disable=unused-argument ): """ Sends a proactive message to a conversation. @@ -166,6 +168,14 @@ async def continue_conversation( # pylint: disable=arguments-differ and is generally found in the `MicrosoftAppId` parameter in `config.py`. :type bot_app_id: :class:`typing.str` """ + if claims_identity: + return await self.continue_conversation_with_claims( + claims_identity=claims_identity, + reference=reference, + audience=audience, + logic=callback, + ) + return await self.process_proactive( self.create_claims_identity(bot_app_id), get_continuation_activity(reference), diff --git a/libraries/botbuilder-core/botbuilder/core/inspection/inspection_middleware.py b/libraries/botbuilder-core/botbuilder/core/inspection/inspection_middleware.py index 2f84b0efd..bf817c1af 100644 --- a/libraries/botbuilder-core/botbuilder/core/inspection/inspection_middleware.py +++ b/libraries/botbuilder-core/botbuilder/core/inspection/inspection_middleware.py @@ -95,10 +95,10 @@ async def _trace_state(self, context: TurnContext) -> Any: ) if self.conversation_state: - bot_state[ - "conversation_state" - ] = InspectionMiddleware._get_serialized_context( - self.conversation_state, context + bot_state["conversation_state"] = ( + InspectionMiddleware._get_serialized_context( + self.conversation_state, context + ) ) await self._invoke_send(context, session, from_state(bot_state)) diff --git a/libraries/botbuilder-core/botbuilder/core/skills/_skill_handler_impl.py b/libraries/botbuilder-core/botbuilder/core/skills/_skill_handler_impl.py index dcd31a8c6..f50cd54ff 100644 --- a/libraries/botbuilder-core/botbuilder/core/skills/_skill_handler_impl.py +++ b/libraries/botbuilder-core/botbuilder/core/skills/_skill_handler_impl.py @@ -133,9 +133,9 @@ async def on_delete_activity( ) async def callback(turn_context: TurnContext): - turn_context.turn_state[ - self.SKILL_CONVERSATION_REFERENCE_KEY - ] = skill_conversation_reference + turn_context.turn_state[self.SKILL_CONVERSATION_REFERENCE_KEY] = ( + skill_conversation_reference + ) await turn_context.delete_activity(activity_id) await self._adapter.continue_conversation( @@ -160,9 +160,9 @@ async def on_update_activity( async def callback(turn_context: TurnContext): nonlocal resource_response - turn_context.turn_state[ - self.SKILL_CONVERSATION_REFERENCE_KEY - ] = skill_conversation_reference + turn_context.turn_state[self.SKILL_CONVERSATION_REFERENCE_KEY] = ( + skill_conversation_reference + ) activity.apply_conversation_reference( skill_conversation_reference.conversation_reference ) @@ -217,9 +217,9 @@ async def _process_activity( async def callback(context: TurnContext): nonlocal resource_response - context.turn_state[ - SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY - ] = skill_conversation_reference + context.turn_state[SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY] = ( + skill_conversation_reference + ) TurnContext.apply_conversation_reference( activity, skill_conversation_reference.conversation_reference diff --git a/libraries/botbuilder-core/botbuilder/core/telemetry_logger_middleware.py b/libraries/botbuilder-core/botbuilder/core/telemetry_logger_middleware.py index 7f65d95d5..d14c3f7f2 100644 --- a/libraries/botbuilder-core/botbuilder/core/telemetry_logger_middleware.py +++ b/libraries/botbuilder-core/botbuilder/core/telemetry_logger_middleware.py @@ -164,9 +164,9 @@ async def fill_receive_event_properties( BotTelemetryClient.track_event method for the BotMessageReceived event. """ properties = { - TelemetryConstants.FROM_ID_PROPERTY: activity.from_property.id - if activity.from_property - else None, + TelemetryConstants.FROM_ID_PROPERTY: ( + activity.from_property.id if activity.from_property else None + ), TelemetryConstants.CONVERSATION_NAME_PROPERTY: activity.conversation.name, TelemetryConstants.LOCALE_PROPERTY: activity.locale, TelemetryConstants.RECIPIENT_ID_PROPERTY: activity.recipient.id, @@ -179,9 +179,9 @@ async def fill_receive_event_properties( and activity.from_property.name and activity.from_property.name.strip() ): - properties[ - TelemetryConstants.FROM_NAME_PROPERTY - ] = activity.from_property.name + properties[TelemetryConstants.FROM_NAME_PROPERTY] = ( + activity.from_property.name + ) if activity.text and activity.text.strip(): properties[TelemetryConstants.TEXT_PROPERTY] = activity.text if activity.speak and activity.speak.strip(): @@ -224,9 +224,9 @@ async def fill_send_event_properties( activity.attachments ) if activity.from_property.name and activity.from_property.name.strip(): - properties[ - TelemetryConstants.FROM_NAME_PROPERTY - ] = activity.from_property.name + properties[TelemetryConstants.FROM_NAME_PROPERTY] = ( + activity.from_property.name + ) if activity.text and activity.text.strip(): properties[TelemetryConstants.TEXT_PROPERTY] = activity.text if activity.speak and activity.speak.strip(): diff --git a/libraries/botbuilder-core/botbuilder/core/turn_context.py b/libraries/botbuilder-core/botbuilder/core/turn_context.py index 2b4d688af..90ab99bd0 100644 --- a/libraries/botbuilder-core/botbuilder/core/turn_context.py +++ b/libraries/botbuilder-core/botbuilder/core/turn_context.py @@ -5,6 +5,7 @@ from copy import copy, deepcopy from datetime import datetime from typing import List, Callable, Union, Dict +from botframework.connector import Channels from botbuilder.schema import ( Activity, ActivityTypes, @@ -328,7 +329,13 @@ def get_conversation_reference(activity: Activity) -> ConversationReference: :return: """ return ConversationReference( - activity_id=activity.id, + activity_id=( + activity.id + if activity.type != ActivityTypes.conversation_update + and activity.channel_id != Channels.direct_line + and activity.channel_id != Channels.webchat + else None + ), user=copy(activity.from_property), bot=copy(activity.recipient), conversation=copy(activity.conversation), diff --git a/libraries/botbuilder-core/tests/skills/test_skill_handler.py b/libraries/botbuilder-core/tests/skills/test_skill_handler.py index 722a944b8..66d79c2ce 100644 --- a/libraries/botbuilder-core/tests/skills/test_skill_handler.py +++ b/libraries/botbuilder-core/tests/skills/test_skill_handler.py @@ -244,9 +244,9 @@ def setUpClass(cls): cls._claims_identity.claims[AuthenticationConstants.AUDIENCE_CLAIM] = cls.bot_id cls._claims_identity.claims[AuthenticationConstants.APP_ID_CLAIM] = cls.skill_id - cls._claims_identity.claims[ - AuthenticationConstants.SERVICE_URL_CLAIM - ] = "http://testbot.com/api/messages" + cls._claims_identity.claims[AuthenticationConstants.SERVICE_URL_CLAIM] = ( + "http://testbot.com/api/messages" + ) cls._conversation_reference = ConversationReference( conversation=ConversationAccount(id=str(uuid4())), service_url="http://testbot.com/api/messages", diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 9a1872952..32e9f2edb 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -595,9 +595,9 @@ async def test_on_teams_members_added_activity(self): mock_connector_client = await SimpleAdapter.create_connector_client( self, turn_context.activity.service_url ) - turn_context.turn_state[ - BotAdapter.BOT_CONNECTOR_CLIENT_KEY - ] = mock_connector_client + turn_context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = ( + mock_connector_client + ) # Act bot = TestingTeamsActivityHandler() diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py index cd2874ad4..0aa005789 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py @@ -56,9 +56,11 @@ async def get_sign_in_resource( turn_context, settings.oath_app_credentials, settings.connection_name, - turn_context.activity.from_property.id - if turn_context.activity and turn_context.activity.from_property - else None, + ( + turn_context.activity.from_property.id + if turn_context.activity and turn_context.activity.from_property + else None + ), ) raise TypeError("OAuthPrompt is not supported by the current adapter") @@ -78,9 +80,11 @@ async def sign_out_user(turn_context: TurnContext, settings: OAuthPromptSettings return await turn_context.adapter.sign_out_user( turn_context, settings.connection_name, - turn_context.activity.from_property.id - if turn_context.activity and turn_context.activity.from_property - else None, + ( + turn_context.activity.from_property.id + if turn_context.activity and turn_context.activity.from_property + else None + ), settings.oath_app_credentials, ) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py index f221708f1..0181e67a2 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py @@ -408,9 +408,9 @@ def __set_exception_context_data(self, exception: Exception): current_dc = current_dc.parent exception.data[type(self).__name__] = { - "active_dialog": None - if self.active_dialog is None - else self.active_dialog.id, + "active_dialog": ( + None if self.active_dialog is None else self.active_dialog.id + ), "parent": None if self.parent is None else self.parent.active_dialog.id, "stack": self.stack, } diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_extensions.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_extensions.py index a5a8a34ab..f9fb67c96 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_extensions.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_extensions.py @@ -50,9 +50,9 @@ async def _internal_run( # get the DialogStateManager configuration dialog_state_manager = DialogStateManager(dialog_context) await dialog_state_manager.load_all_scopes() - dialog_context.context.turn_state[ - dialog_state_manager.__class__.__name__ - ] = dialog_state_manager + dialog_context.context.turn_state[dialog_state_manager.__class__.__name__] = ( + dialog_state_manager + ) # Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. @@ -130,9 +130,11 @@ async def __inner_run( type=ActivityTypes.end_of_conversation, value=result.result, locale=turn_context.activity.locale, - code=EndOfConversationCodes.completed_successfully - if result.status == DialogTurnStatus.Complete - else EndOfConversationCodes.user_cancelled, + code=( + EndOfConversationCodes.completed_successfully + if result.status == DialogTurnStatus.Complete + else EndOfConversationCodes.user_cancelled + ), ) await turn_context.send_activity(activity) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_manager.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_manager.py index 84dc108c2..df7a5569e 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_manager.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_manager.py @@ -291,9 +291,11 @@ async def handle_skill_on_turn( type=ActivityTypes.end_of_conversation, value=turn_result.result, locale=turn_context.activity.locale, - code=EndOfConversationCodes.completed_successfully - if turn_result.status == DialogTurnStatus.Complete - else EndOfConversationCodes.user_cancelled, + code=( + EndOfConversationCodes.completed_successfully + if turn_result.status == DialogTurnStatus.Complete + else EndOfConversationCodes.user_cancelled + ), ) await turn_context.send_activity(activity) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/memory/dialog_state_manager.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/memory/dialog_state_manager.py index c1bf6c106..a11ab9c3a 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/memory/dialog_state_manager.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/memory/dialog_state_manager.py @@ -94,9 +94,9 @@ def __init__( self._configuration.path_resolvers.append(path_resolver) # cache for any other new dialog_state_manager instances in this turn. - dialog_context.context.turn_state[ - self._configuration.__class__.__name__ - ] = self._configuration + dialog_context.context.turn_state[self._configuration.__class__.__name__] = ( + self._configuration + ) def __len__(self) -> int: """ diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py index 55fae561f..e0db0e2bb 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py @@ -415,9 +415,9 @@ async def _recognize_token( state.scope, ) - context.turn_state[ - BotAdapter.BOT_CONNECTOR_CLIENT_KEY - ] = connector_client + context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = ( + connector_client + ) elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py index 81b67de18..e5be50c24 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py @@ -60,17 +60,17 @@ async def begin_dialog(self, dialog_context: DialogContext, options: object = No ) # Store delivery mode in dialog state for later use. - dialog_context.active_dialog.state[ - self._deliver_mode_state_key - ] = dialog_args.activity.delivery_mode + dialog_context.active_dialog.state[self._deliver_mode_state_key] = ( + dialog_args.activity.delivery_mode + ) # Create the conversationId and store it in the dialog context state so we can use it later skill_conversation_id = await self._create_skill_conversation_id( dialog_context.context, dialog_context.context.activity ) - dialog_context.active_dialog.state[ - SkillDialog.SKILLCONVERSATIONIDSTATEKEY - ] = skill_conversation_id + dialog_context.active_dialog.state[SkillDialog.SKILLCONVERSATIONIDSTATEKEY] = ( + skill_conversation_id + ) # Send the activity to the skill. eoc_activity = await self._send_to_skill( diff --git a/libraries/botbuilder-dialogs/tests/test_dialog_manager.py b/libraries/botbuilder-dialogs/tests/test_dialog_manager.py index 1846ce42f..57b264e60 100644 --- a/libraries/botbuilder-dialogs/tests/test_dialog_manager.py +++ b/libraries/botbuilder-dialogs/tests/test_dialog_manager.py @@ -120,17 +120,13 @@ async def logic(context: TurnContext): if test_case != SkillFlowTestCase.root_bot_only: # Create a skill ClaimsIdentity and put it in turn_state so isSkillClaim() returns True. claims_identity = ClaimsIdentity({}, False) - claims_identity.claims[ - "ver" - ] = "2.0" # AuthenticationConstants.VersionClaim - claims_identity.claims[ - "aud" - ] = ( + claims_identity.claims["ver"] = ( + "2.0" # AuthenticationConstants.VersionClaim + ) + claims_identity.claims["aud"] = ( SimpleComponentDialog.skill_bot_id ) # AuthenticationConstants.AudienceClaim - claims_identity.claims[ - "azp" - ] = ( + claims_identity.claims["azp"] = ( SimpleComponentDialog.parent_bot_id ) # AuthenticationConstants.AuthorizedParty context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py index cf46a0081..b09b3ac93 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py @@ -27,7 +27,6 @@ class BotFrameworkHttpClient(BotFrameworkClient): - """ A skill host adapter that implements the API to forward activity to a skill and implements routing ChannelAPI calls from the skill up through the bot/adapter. @@ -49,7 +48,6 @@ def __init__( self._credential_provider = credential_provider self._channel_provider = channel_provider self._logger = logger - self._session = aiohttp.ClientSession() async def post_activity( self, @@ -127,11 +125,14 @@ async def _post_content( ) json_content = json.dumps(activity.serialize()) - resp = await self._session.post( - to_url, - data=json_content.encode("utf-8"), - headers=headers_dict, - ) + + async with aiohttp.ClientSession() as session: + resp = await session.post( + to_url, + data=json_content.encode("utf-8"), + headers=headers_dict, + ) + resp.raise_for_status() data = (await resp.read()).decode() return resp.status, json.loads(data) if data else None diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/skills/aio_http_client_factory.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/skills/aio_http_client_factory.py index 84235e86b..62d4ae539 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/skills/aio_http_client_factory.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/skills/aio_http_client_factory.py @@ -31,13 +31,11 @@ async def read_content_str(self) -> str: class _HttpClientImplementation(HttpClientBase): - def __init__(self) -> None: - self._session = ClientSession() - async def post(self, *, request: HttpRequest) -> HttpResponseBase: - aio_response = await self._session.post( - request.request_uri, data=request.content, headers=request.headers - ) + async with ClientSession() as session: + aio_response = await session.post( + request.request_uri, data=request.content, headers=request.headers + ) return _HttpResponseImpl(aio_response) diff --git a/libraries/botbuilder-integration-aiohttp/tests/skills/test_skill_http_client.py b/libraries/botbuilder-integration-aiohttp/tests/skills/test_skill_http_client.py index b3db490b7..eba3352e1 100644 --- a/libraries/botbuilder-integration-aiohttp/tests/skills/test_skill_http_client.py +++ b/libraries/botbuilder-integration-aiohttp/tests/skills/test_skill_http_client.py @@ -200,6 +200,5 @@ async def _create_http_client_with_mock_handler( # pylint: disable=protected-access client = SkillHttpClient(Mock(), id_factory, channel_provider) client._post_content = value_function - await client._session.close() return client diff --git a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py index a458ce5bb..b39511d82 100644 --- a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py @@ -77,9 +77,9 @@ def __get_msal_app(self): client_credential={ "thumbprint": self.certificate_thumbprint, "private_key": self.certificate_private_key, - "public_certificate": self.certificate_public - if self.certificate_public - else None, + "public_certificate": ( + self.certificate_public if self.certificate_public else None + ), }, ) diff --git a/libraries/botframework-connector/botframework/connector/auth/government_constants.py b/libraries/botframework-connector/botframework/connector/auth/government_constants.py index aba16e396..3e109d3b6 100644 --- a/libraries/botframework-connector/botframework/connector/auth/government_constants.py +++ b/libraries/botframework-connector/botframework/connector/auth/government_constants.py @@ -5,7 +5,6 @@ class GovernmentConstants(ABC): - """ Government Channel Service property value """ diff --git a/libraries/botframework-connector/tests/test_skill_validation.py b/libraries/botframework-connector/tests/test_skill_validation.py index a7667c3d7..bfa4951ce 100644 --- a/libraries/botframework-connector/tests/test_skill_validation.py +++ b/libraries/botframework-connector/tests/test_skill_validation.py @@ -37,9 +37,9 @@ def test_is_skill_claim_test(self): assert not SkillValidation.is_skill_claim(claims) # Emulator Audience claim - claims[ - AuthenticationConstants.AUDIENCE_CLAIM - ] = AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER + claims[AuthenticationConstants.AUDIENCE_CLAIM] = ( + AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER + ) assert not SkillValidation.is_skill_claim(claims) # No AppId claim @@ -53,9 +53,9 @@ def test_is_skill_claim_test(self): # Anonymous skill app id del claims[AuthenticationConstants.APP_ID_CLAIM] - claims[ - AuthenticationConstants.APP_ID_CLAIM - ] = AuthenticationConstants.ANONYMOUS_SKILL_APP_ID + claims[AuthenticationConstants.APP_ID_CLAIM] = ( + AuthenticationConstants.ANONYMOUS_SKILL_APP_ID + ) assert SkillValidation.is_skill_claim(claims) # All checks pass, should be good now From c6182f146383927c45c0408b5763183ee1a37148 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 13:27:32 -0500 Subject: [PATCH 049/102] --- (#2109) updated-dependencies: - dependency-name: requests dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tracyboehrer --- tests/functional-tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional-tests/requirements.txt b/tests/functional-tests/requirements.txt index d846e6294..d00d7a830 100644 --- a/tests/functional-tests/requirements.txt +++ b/tests/functional-tests/requirements.txt @@ -1,2 +1,2 @@ -requests==2.31.0 +requests==2.32.0 aiounittest==1.3.0 From 8e127e7592f417ea4027e7c32a50292026c2f7a0 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 23 May 2024 16:15:16 -0500 Subject: [PATCH 050/102] Updated README with supported Python versions (#2113) Co-authored-by: Tracy Boehrer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39b134ac1..aedd0482b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If you want to debug an issue, would like to [contribute](#contributing-code), o ### Prerequisites - [Git](https://git-scm.com/downloads) -- [Python 3.8.17](https://www.python.org/downloads/) +- [Python 3.8.17 - 3.11.x](https://www.python.org/downloads/) Python "Virtual Environments" allow Python packages to be installed in an isolated location for a particular application, rather than being installed globally, as such it is common practice to use them. Click [here](https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments) to learn more about creating _and activating_ Virtual Environments in Python. From b33dc49e1cc51c893b1585974bafddaab68ebbe4 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 28 May 2024 11:45:36 -0500 Subject: [PATCH 051/102] Bump aiohttp in SDK pacakges (#2115) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-ai/setup.py | 2 +- libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../botbuilder-integration-applicationinsights-aiohttp/setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 989f1755c..bc9f83beb 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.16.0", "botbuilder-core==4.16.0", - "aiohttp==3.9.3", + "aiohttp==3.9.4", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index fd743ecf3..85b06a449 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.16.0", "botbuilder-core==4.16.0", "yarl>=1.8.1", - "aiohttp==3.9.3", + "aiohttp==3.9.4", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index 185324083..5429b1cf3 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.9.3", + "aiohttp==3.9.4", "botbuilder-schema==4.16.0", "botframework-connector==4.16.0", "botbuilder-core==4.16.0", From bc3b6f4125e6f4ca5e6cae2dd25c62233e8ef508 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 30 May 2024 09:44:27 -0500 Subject: [PATCH 052/102] Parity in CloudAdapterBase.update_activity to use correct activity id (#2120) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py index dac83b0ce..454861257 100644 --- a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py +++ b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py @@ -120,7 +120,7 @@ async def update_activity(self, context: TurnContext, activity: Activity): raise Error("Unable to extract ConnectorClient from turn context.") response = await connector_client.conversations.update_activity( - activity.conversation.id, activity.reply_to_id, activity + activity.conversation.id, activity.id, activity ) response_id = response.id if response and response.id else None From 01449b5a9f33f55297e420c1c0cc2403c63ad67c Mon Sep 17 00:00:00 2001 From: harvey-k <12886479+harvey-k@users.noreply.github.com> Date: Fri, 31 May 2024 14:23:35 -0400 Subject: [PATCH 053/102] fix populating token issuers (#2117) #2116 Co-authored-by: tracyboehrer --- .../botframework/connector/auth/skill_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py index b708e27cb..8c35f1b0a 100644 --- a/libraries/botframework-connector/botframework/connector/auth/skill_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/skill_validation.py @@ -120,7 +120,7 @@ async def authenticate_channel_token( ) if auth_configuration.valid_token_issuers: - token_validation_parameters.issuer.append( + token_validation_parameters.issuer.extend( auth_configuration.valid_token_issuers ) From 4b194836a2d540b58bb8937e8b2c9808ec202949 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Tue, 11 Jun 2024 21:59:50 +0530 Subject: [PATCH 054/102] Resolved the inconsistency in response formats between the Python Echo bot template and the C# and JavaScript Echo bot templates. (#2124) --- generators/app/templates/echo/{{cookiecutter.bot_name}}/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/bot.py b/generators/app/templates/echo/{{cookiecutter.bot_name}}/bot.py index ca7562263..89b2eefdb 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/bot.py +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/bot.py @@ -9,7 +9,7 @@ class MyBot(ActivityHandler): # See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. async def on_message_activity(self, turn_context: TurnContext): - await turn_context.send_activity(f"You said '{ turn_context.activity.text }'") + await turn_context.send_activity(f"Echo: '{ turn_context.activity.text }'") async def on_members_added_activity( self, From 03f9d10ddfd5f6f3fea9705e5e4057fb6819eac6 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 21 Jun 2024 07:39:01 -0500 Subject: [PATCH 055/102] Removed invalid exchange_token check (#2121) Co-authored-by: Tracy Boehrer --- .../botbuilder/dialogs/prompts/oauth_prompt.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py index e0db0e2bb..c5b066913 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py @@ -477,19 +477,6 @@ async def _recognize_token( " 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. token_exchange_response = None From fc9451423cd0ff06c852a1fbd286f53a7f2b4e3f Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 21 Jun 2024 09:03:06 -0500 Subject: [PATCH 056/102] Bump azure-storage-queue (#2126) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-azure/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-azure/setup.py b/libraries/botbuilder-azure/setup.py index 8d698a34f..85b4bf5e5 100644 --- a/libraries/botbuilder-azure/setup.py +++ b/libraries/botbuilder-azure/setup.py @@ -7,7 +7,7 @@ REQUIRES = [ "azure-cosmos==3.2.0", "azure-storage-blob==12.7.0", - "azure-storage-queue==12.1.5", + "azure-storage-queue==12.4.0", "botbuilder-schema==4.16.0", "botframework-connector==4.16.0", "jsonpickle>=1.2,<1.5", From d7cd9374104e19d1599635036f304480d2ff4274 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 21 Jun 2024 09:34:42 -0500 Subject: [PATCH 057/102] aiohttp 3.9.5 (#2127) Co-authored-by: Tracy Boehrer --- libraries/botbuilder-ai/setup.py | 2 +- libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../botbuilder-integration-applicationinsights-aiohttp/setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index bc9f83beb..bd954661b 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.16.0", "botbuilder-core==4.16.0", - "aiohttp==3.9.4", + "aiohttp==3.9.5", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index 85b06a449..b8d84a9aa 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.16.0", "botbuilder-core==4.16.0", "yarl>=1.8.1", - "aiohttp==3.9.4", + "aiohttp==3.9.5", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index 5429b1cf3..0a465eaf1 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.9.4", + "aiohttp==3.9.5", "botbuilder-schema==4.16.0", "botframework-connector==4.16.0", "botbuilder-core==4.16.0", From 579888d91a980c656f83c3f6614d0e2f4f913ec8 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Jul 2024 05:53:36 -0500 Subject: [PATCH 058/102] UserAssigned MSI support (#2129) * Added ManagedIdentity * Missing ConfigurationServiceClientCredentialFactory awaits * ManagedIdentityAppCredentials needs ManagedIdentity dict * Added missing PermissionError descriptions * Black reformatting in botbuilder-core --------- Co-authored-by: Tracy Boehrer --- .../core/channel_service_handler.py | 4 +- .../integration/aiohttp/cloud_adapter.py | 2 +- ...ation_service_client_credential_factory.py | 68 ++++++++++++++++--- .../botframework/connector/auth/__init__.py | 2 + ...ameterized_bot_framework_authentication.py | 12 ++-- .../connector/auth/jwt_token_validation.py | 2 +- .../auth/managedidentity_app_credentials.py | 56 +++++++++++++++ ...ntity_service_client_credential_factory.py | 39 +++++++++++ .../botframework-connector/requirements.txt | 2 +- libraries/botframework-connector/setup.py | 2 +- 10 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 libraries/botframework-connector/botframework/connector/auth/managedidentity_app_credentials.py create mode 100644 libraries/botframework-connector/botframework/connector/auth/managedidentity_service_client_credential_factory.py diff --git a/libraries/botbuilder-core/botbuilder/core/channel_service_handler.py b/libraries/botbuilder-core/botbuilder/core/channel_service_handler.py index 8de4da56d..2b819d00c 100644 --- a/libraries/botbuilder-core/botbuilder/core/channel_service_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/channel_service_handler.py @@ -504,7 +504,9 @@ async def _authenticate(self, auth_header: str) -> ClaimsIdentity: ) if not is_auth_disabled: # No auth header. Auth is required. Request is not authorized. - raise PermissionError() + raise PermissionError( + "Authorization is required but has been disabled." + ) # In the scenario where Auth is disabled, we still want to have the # IsAuthenticated flag set in the ClaimsIdentity. To do this requires diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py index 0aa2ba8af..0f9131871 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py @@ -107,7 +107,7 @@ async def process( return Response(status=201) else: raise HTTPMethodNotAllowed - except (HTTPUnauthorized, PermissionError) as _: + except PermissionError: raise HTTPUnauthorized async def _connect( 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 34e7c7644..6379e16b6 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 @@ -4,18 +4,23 @@ from logging import Logger from typing import Any +from msrest.authentication import Authentication + from botframework.connector.auth import PasswordServiceClientCredentialFactory +from botframework.connector.auth import ManagedIdentityServiceClientCredentialsFactory +from botframework.connector.auth import ServiceClientCredentialsFactory -class ConfigurationServiceClientCredentialFactory( - PasswordServiceClientCredentialFactory -): +class ConfigurationServiceClientCredentialFactory(ServiceClientCredentialsFactory): def __init__(self, configuration: Any, *, logger: Logger = None) -> None: + self._inner = None + app_type = ( configuration.APP_TYPE if hasattr(configuration, "APP_TYPE") else "MultiTenant" - ) + ).lower() + app_id = configuration.APP_ID if hasattr(configuration, "APP_ID") else None app_password = ( configuration.APP_PASSWORD @@ -24,10 +29,25 @@ def __init__(self, configuration: Any, *, logger: Logger = None) -> None: ) app_tenantid = None - if app_type == "UserAssignedMsi": - raise Exception("UserAssignedMsi APP_TYPE is not supported") + if app_type == "userassignedmsi": + if not app_id: + raise Exception("Property 'APP_ID' is expected in configuration object") + + app_tenantid = ( + configuration.APP_TENANTID + if hasattr(configuration, "APP_TENANTID") + else None + ) + if not app_tenantid: + raise Exception( + "Property 'APP_TENANTID' is expected in configuration object" + ) + + self._inner = ManagedIdentityServiceClientCredentialsFactory( + app_id, logger=logger + ) - if app_type == "SingleTenant": + elif app_type == "singletenant": app_tenantid = ( configuration.APP_TENANTID if hasattr(configuration, "APP_TENANTID") @@ -45,4 +65,36 @@ def __init__(self, configuration: Any, *, logger: Logger = None) -> None: "Property 'APP_TENANTID' is expected in configuration object" ) - super().__init__(app_id, app_password, app_tenantid, logger=logger) + self._inner = PasswordServiceClientCredentialFactory( + app_id, app_password, app_tenantid, logger=logger + ) + + # Default to MultiTenant + else: + 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" + ) + + self._inner = PasswordServiceClientCredentialFactory( + app_id, app_password, None, logger=logger + ) + + async def is_valid_app_id(self, app_id: str) -> bool: + return await self._inner.is_valid_app_id(app_id) + + async def is_authentication_disabled(self) -> bool: + return await self._inner.is_authentication_disabled() + + async def create_credentials( + self, + app_id: str, + oauth_scope: str, + login_endpoint: str, + validate_authority: bool, + ) -> Authentication: + return await self._inner.create_credentials( + app_id, oauth_scope, login_endpoint, validate_authority + ) diff --git a/libraries/botframework-connector/botframework/connector/auth/__init__.py b/libraries/botframework-connector/botframework/connector/auth/__init__.py index fd34db01a..8747a03c8 100644 --- a/libraries/botframework-connector/botframework/connector/auth/__init__.py +++ b/libraries/botframework-connector/botframework/connector/auth/__init__.py @@ -27,3 +27,5 @@ from .service_client_credentials_factory import * from .user_token_client import * from .authentication_configuration import * +from .managedidentity_app_credentials import * +from .managedidentity_service_client_credential_factory import * 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 1388094fe..3419c2099 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 @@ -473,11 +473,11 @@ async def _government_channel_validation_validate_identity( ): if identity is None: # No valid identity. Not Authorized. - raise PermissionError() + raise PermissionError("Identity missing") if not identity.is_authenticated: # The token is in some way invalid. Not Authorized. - raise PermissionError() + raise PermissionError("Invalid token") # Now check that the AppID in the claim set matches # what we're looking for. Note that in a multi-tenant bot, this value @@ -487,12 +487,12 @@ async def _government_channel_validation_validate_identity( # Look for the "aud" claim, but only if issued from the Bot Framework issuer = identity.get_claim_value(AuthenticationConstants.ISSUER_CLAIM) if issuer != self._to_bot_from_channel_token_issuer: - raise PermissionError() + raise PermissionError("'iss' claim missing") app_id = identity.get_claim_value(AuthenticationConstants.AUDIENCE_CLAIM) if not app_id: # The relevant audience Claim MUST be present. Not Authorized. - raise PermissionError() + raise PermissionError("'aud' claim missing") # The AppId from the claim in the token must match the AppId specified by the developer. # In this case, the token is destined for the app, so we find the app ID in the audience claim. @@ -507,8 +507,8 @@ async def _government_channel_validation_validate_identity( ) if not service_url_claim: # Claim must be present. Not Authorized. - raise PermissionError() + raise PermissionError("'serviceurl' claim missing") if service_url_claim != service_url: # Claim must match. Not Authorized. - raise PermissionError() + raise PermissionError("Invalid 'serviceurl' claim") 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 659559f14..a0e937156 100644 --- a/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py +++ b/libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py @@ -46,7 +46,7 @@ async def authenticate_request( auth_is_disabled = await credentials.is_authentication_disabled() if not auth_is_disabled: # No Auth Header. Auth is required. Request is not authorized. - raise PermissionError("Unauthorized Access. Request is not authorized") + raise PermissionError("Required Authorization token was not supplied") # Check if the activity is for a skill call and is coming from the Emulator. try: diff --git a/libraries/botframework-connector/botframework/connector/auth/managedidentity_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/managedidentity_app_credentials.py new file mode 100644 index 000000000..568eb19e2 --- /dev/null +++ b/libraries/botframework-connector/botframework/connector/auth/managedidentity_app_credentials.py @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from abc import ABC + +import msal +import requests + +from .app_credentials import AppCredentials +from .microsoft_app_credentials import MicrosoftAppCredentials + + +class ManagedIdentityAppCredentials(AppCredentials, ABC): + """ + AppCredentials implementation using application ID and password. + """ + + global_token_cache = msal.TokenCache() + + def __init__(self, app_id: str, oauth_scope: str = None): + # super will set proper scope and endpoint. + super().__init__( + app_id=app_id, + oauth_scope=oauth_scope, + ) + + self._managed_identity = {"ManagedIdentityIdType": "ClientId", "Id": app_id} + + self.app = None + + @staticmethod + def empty(): + return MicrosoftAppCredentials("", "") + + def get_access_token(self, force_refresh: bool = False) -> str: + """ + Implementation of AppCredentials.get_token. + :return: The access token for the given app id and password. + """ + + # Firstly, looks up a token from cache + # Since we are looking for token for the current app, NOT for an end user, + # notice we give account parameter as None. + auth_token = self.__get_msal_app().acquire_token_for_client( + resource=self.oauth_scope + ) + return auth_token["access_token"] + + def __get_msal_app(self): + if not self.app: + self.app = msal.ManagedIdentityClient( + self._managed_identity, + http_client=requests.Session(), + token_cache=ManagedIdentityAppCredentials.global_token_cache, + ) + return self.app diff --git a/libraries/botframework-connector/botframework/connector/auth/managedidentity_service_client_credential_factory.py b/libraries/botframework-connector/botframework/connector/auth/managedidentity_service_client_credential_factory.py new file mode 100644 index 000000000..61bf2a12b --- /dev/null +++ b/libraries/botframework-connector/botframework/connector/auth/managedidentity_service_client_credential_factory.py @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from logging import Logger + +from msrest.authentication import Authentication + +from .managedidentity_app_credentials import ManagedIdentityAppCredentials +from .microsoft_app_credentials import MicrosoftAppCredentials +from .service_client_credentials_factory import ServiceClientCredentialsFactory + + +class ManagedIdentityServiceClientCredentialsFactory(ServiceClientCredentialsFactory): + def __init__(self, app_id: str = None, *, logger: Logger = None) -> None: + self.app_id = app_id + self._logger = logger + + async def is_valid_app_id(self, app_id: str) -> bool: + return app_id == self.app_id + + async def is_authentication_disabled(self) -> bool: + return not self.app_id + + async def create_credentials( + self, + app_id: str, + oauth_scope: str, + login_endpoint: str, + validate_authority: bool, + ) -> Authentication: + if await self.is_authentication_disabled(): + return MicrosoftAppCredentials.empty() + + if not await self.is_valid_app_id(app_id): + raise Exception("Invalid app_id") + + credentials = ManagedIdentityAppCredentials(app_id, oauth_scope) + + return credentials diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index a762e8813..3c789ac2c 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -3,4 +3,4 @@ botbuilder-schema==4.16.0 requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 -msal==1.* +msal>=1.29.0 diff --git a/libraries/botframework-connector/setup.py b/libraries/botframework-connector/setup.py index ca4b72025..ad269fe60 100644 --- a/libraries/botframework-connector/setup.py +++ b/libraries/botframework-connector/setup.py @@ -11,7 +11,7 @@ # "requests>=2.23.0,<2.26", "PyJWT>=2.4.0", "botbuilder-schema==4.16.0", - "msal==1.*", + "msal>=1.29.0", ] root = os.path.abspath(os.path.dirname(__file__)) From c20b27b4f7c3f9983a352f4718b8f517d216ac35 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Jul 2024 06:07:01 -0500 Subject: [PATCH 059/102] Bump SDK version (#2131) Co-authored-by: Tracy Boehrer --- README.md | 2 +- .../botbuilder/adapters/slack/about.py | 2 +- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-adapters-slack/setup.py | 6 +++--- libraries/botbuilder-ai/botbuilder/ai/about.py | 2 +- libraries/botbuilder-ai/requirements.txt | 4 ++-- libraries/botbuilder-ai/setup.py | 4 ++-- .../botbuilder/applicationinsights/about.py | 2 +- libraries/botbuilder-applicationinsights/requirements.txt | 2 +- libraries/botbuilder-applicationinsights/setup.py | 6 +++--- libraries/botbuilder-azure/botbuilder/azure/about.py | 2 +- libraries/botbuilder-azure/setup.py | 4 ++-- libraries/botbuilder-core/botbuilder/core/about.py | 2 +- libraries/botbuilder-core/requirements.txt | 6 +++--- libraries/botbuilder-core/setup.py | 8 ++++---- libraries/botbuilder-dialogs/botbuilder/dialogs/about.py | 2 +- libraries/botbuilder-dialogs/requirements.txt | 6 +++--- libraries/botbuilder-dialogs/setup.py | 6 +++--- .../botbuilder/integration/aiohttp/about.py | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 4 ++-- libraries/botbuilder-integration-aiohttp/setup.py | 8 ++++---- .../integration/applicationinsights/aiohttp/about.py | 2 +- .../setup.py | 8 ++++---- libraries/botbuilder-schema/setup.py | 2 +- libraries/botbuilder-testing/botbuilder/testing/about.py | 2 +- libraries/botbuilder-testing/requirements.txt | 6 +++--- libraries/botbuilder-testing/setup.py | 8 ++++---- .../botframework/connector/about.py | 2 +- libraries/botframework-connector/requirements.txt | 2 +- libraries/botframework-connector/setup.py | 4 ++-- .../botframework/streaming/about.py | 2 +- libraries/botframework-streaming/requirements.txt | 4 ++-- libraries/botframework-streaming/setup.py | 6 +++--- 33 files changed, 65 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index aedd0482b..92a7c1332 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | Code Style | |----|---------------|--------------|-----------------|--| -| Main | 4.16.0 Builds | [![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/Python-CI-PR-yaml?branchName=main)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=771&branchName=main) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-python/badge.svg?branch=HEAD)](https://coveralls.io/github/microsoft/botbuilder-python?branch=HEAD) | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | +| Main | 4.17.0 Builds | [![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/Python-CI-PR-yaml?branchName=main)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=771&branchName=main) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-python/badge.svg?branch=HEAD)](https://coveralls.io/github/microsoft/botbuilder-python?branch=HEAD) | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | ## Packages diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py index 2da3fe8e7..d0c18dbaa 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-adapters-slack" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 7fd82d816..8d2c7b043 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ aiohttp==3.9.5 pyslack -botbuilder-core==4.16.0 +botbuilder-core==4.17.0 slackclient diff --git a/libraries/botbuilder-adapters-slack/setup.py b/libraries/botbuilder-adapters-slack/setup.py index d3eb8cbd1..25fc99ed8 100644 --- a/libraries/botbuilder-adapters-slack/setup.py +++ b/libraries/botbuilder-adapters-slack/setup.py @@ -5,9 +5,9 @@ from setuptools import setup REQUIRES = [ - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botbuilder-core==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botbuilder-core==4.17.0", "pyslack", "slackclient", ] diff --git a/libraries/botbuilder-ai/botbuilder/ai/about.py b/libraries/botbuilder-ai/botbuilder/ai/about.py index 22d69d517..e063c5499 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/about.py +++ b/libraries/botbuilder-ai/botbuilder/ai/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-ai" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-ai/requirements.txt b/libraries/botbuilder-ai/requirements.txt index cea1e6ded..232724deb 100644 --- a/libraries/botbuilder-ai/requirements.txt +++ b/libraries/botbuilder-ai/requirements.txt @@ -1,6 +1,6 @@ msrest== 0.7.* -botbuilder-schema==4.16.0 -botbuilder-core==4.16.0 +botbuilder-schema==4.17.0 +botbuilder-core==4.17.0 requests==2.32.0 aiounittest==1.3.0 azure-cognitiveservices-language-luis==0.2.0 \ No newline at end of file diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index bd954661b..105f1a4c9 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -6,8 +6,8 @@ REQUIRES = [ "azure-cognitiveservices-language-luis==0.2.0", - "botbuilder-schema==4.16.0", - "botbuilder-core==4.16.0", + "botbuilder-schema==4.17.0", + "botbuilder-core==4.17.0", "aiohttp==3.9.5", ] diff --git a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py index 74f9edbb5..b36e7c9b3 100644 --- a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py +++ b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-applicationinsights" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-applicationinsights/requirements.txt b/libraries/botbuilder-applicationinsights/requirements.txt index dd908fd7c..dcdbb2ecb 100644 --- a/libraries/botbuilder-applicationinsights/requirements.txt +++ b/libraries/botbuilder-applicationinsights/requirements.txt @@ -1,3 +1,3 @@ msrest== 0.7.* -botbuilder-core==4.16.0 +botbuilder-core==4.17.0 aiounittest==1.3.0 \ No newline at end of file diff --git a/libraries/botbuilder-applicationinsights/setup.py b/libraries/botbuilder-applicationinsights/setup.py index 8b9dc8107..0932ff98f 100644 --- a/libraries/botbuilder-applicationinsights/setup.py +++ b/libraries/botbuilder-applicationinsights/setup.py @@ -6,9 +6,9 @@ REQUIRES = [ "applicationinsights==0.11.9", - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botbuilder-core==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botbuilder-core==4.17.0", ] TESTS_REQUIRES = [ "aiounittest==1.3.0", diff --git a/libraries/botbuilder-azure/botbuilder/azure/about.py b/libraries/botbuilder-azure/botbuilder/azure/about.py index 73d05308a..2b8d35387 100644 --- a/libraries/botbuilder-azure/botbuilder/azure/about.py +++ b/libraries/botbuilder-azure/botbuilder/azure/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-azure" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-azure/setup.py b/libraries/botbuilder-azure/setup.py index 85b4bf5e5..04fd479cb 100644 --- a/libraries/botbuilder-azure/setup.py +++ b/libraries/botbuilder-azure/setup.py @@ -8,8 +8,8 @@ "azure-cosmos==3.2.0", "azure-storage-blob==12.7.0", "azure-storage-queue==12.4.0", - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", "jsonpickle>=1.2,<1.5", ] TEST_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botbuilder-core/botbuilder/core/about.py b/libraries/botbuilder-core/botbuilder/core/about.py index b684dfef7..5220c09e6 100644 --- a/libraries/botbuilder-core/botbuilder/core/about.py +++ b/libraries/botbuilder-core/botbuilder/core/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-core" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index d9a4bc2f1..6ce30f68f 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -1,7 +1,7 @@ msrest== 0.7.* -botframework-connector==4.16.0 -botbuilder-schema==4.16.0 -botframework-streaming==4.16.0 +botframework-connector==4.17.0 +botbuilder-schema==4.17.0 +botframework-streaming==4.17.0 requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botbuilder-core/setup.py b/libraries/botbuilder-core/setup.py index 6b4ec5929..a4a0ed1af 100644 --- a/libraries/botbuilder-core/setup.py +++ b/libraries/botbuilder-core/setup.py @@ -4,11 +4,11 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" REQUIRES = [ - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botframework-streaming==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botframework-streaming==4.17.0", "jsonpickle>=1.2,<1.5", ] diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py index ac3c0bd7f..5885a1a1e 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-dialogs" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 2ee28a093..920200124 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -1,7 +1,7 @@ msrest== 0.7.* -botframework-connector==4.16.0 -botbuilder-schema==4.16.0 -botbuilder-core==4.16.0 +botframework-connector==4.17.0 +botbuilder-schema==4.17.0 +botbuilder-core==4.17.0 requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botbuilder-dialogs/setup.py b/libraries/botbuilder-dialogs/setup.py index 8b2a44a27..8cedaa53c 100644 --- a/libraries/botbuilder-dialogs/setup.py +++ b/libraries/botbuilder-dialogs/setup.py @@ -13,9 +13,9 @@ "recognizers-text>=1.0.2a1", "recognizers-text-choice>=1.0.2a1", "babel==2.9.1", - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botbuilder-core==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botbuilder-core==4.17.0", ] TEST_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py index fd45f9491..e5cd51eee 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/about.py @@ -5,7 +5,7 @@ __title__ = "botbuilder-integration-aiohttp" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 1a1057e11..9ce978580 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* -botframework-connector==4.16.0 -botbuilder-schema==4.16.0 +botframework-connector==4.17.0 +botbuilder-schema==4.17.0 aiohttp==3.9.5 diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index b8d84a9aa..891647bb7 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -4,11 +4,11 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" REQUIRES = [ - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botbuilder-core==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botbuilder-core==4.17.0", "yarl>=1.8.1", "aiohttp==3.9.5", ] diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py index ab88a6efe..cfaca1e0f 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/botbuilder/integration/applicationinsights/aiohttp/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-integration-applicationinsights-aiohttp" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index 0a465eaf1..d40487403 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -7,10 +7,10 @@ REQUIRES = [ "applicationinsights>=0.11.9", "aiohttp==3.9.5", - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", - "botbuilder-core==4.16.0", - "botbuilder-applicationinsights==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", + "botbuilder-core==4.17.0", + "botbuilder-applicationinsights==4.17.0", ] TESTS_REQUIRES = [ "aiounittest==1.3.0", diff --git a/libraries/botbuilder-schema/setup.py b/libraries/botbuilder-schema/setup.py index dba58aa60..2075a5f20 100644 --- a/libraries/botbuilder-schema/setup.py +++ b/libraries/botbuilder-schema/setup.py @@ -5,7 +5,7 @@ from setuptools import setup NAME = "botbuilder-schema" -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" REQUIRES = ["msrest== 0.7.*", "urllib3<2.0.0"] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-testing/botbuilder/testing/about.py b/libraries/botbuilder-testing/botbuilder/testing/about.py index 89af35e45..dca57a9fa 100644 --- a/libraries/botbuilder-testing/botbuilder/testing/about.py +++ b/libraries/botbuilder-testing/botbuilder/testing/about.py @@ -6,7 +6,7 @@ __title__ = "botbuilder-testing" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botbuilder-testing/requirements.txt b/libraries/botbuilder-testing/requirements.txt index b19d84f12..7bca77c2c 100644 --- a/libraries/botbuilder-testing/requirements.txt +++ b/libraries/botbuilder-testing/requirements.txt @@ -1,4 +1,4 @@ -botbuilder-schema==4.16.0 -botbuilder-core==4.16.0 -botbuilder-dialogs==4.16.0 +botbuilder-schema==4.17.0 +botbuilder-core==4.17.0 +botbuilder-dialogs==4.17.0 aiounittest==1.4.0 diff --git a/libraries/botbuilder-testing/setup.py b/libraries/botbuilder-testing/setup.py index e7dc81fd7..9fed4e3ac 100644 --- a/libraries/botbuilder-testing/setup.py +++ b/libraries/botbuilder-testing/setup.py @@ -5,10 +5,10 @@ from setuptools import setup REQUIRES = [ - "botbuilder-schema==4.16.0", - "botbuilder-core==4.16.0", - "botbuilder-dialogs==4.16.0", - "botbuilder-azure==4.16.0", + "botbuilder-schema==4.17.0", + "botbuilder-core==4.17.0", + "botbuilder-dialogs==4.17.0", + "botbuilder-azure==4.17.0", "pytest~=7.3.1", ] diff --git a/libraries/botframework-connector/botframework/connector/about.py b/libraries/botframework-connector/botframework/connector/about.py index 9f11a65e3..7bda53edb 100644 --- a/libraries/botframework-connector/botframework/connector/about.py +++ b/libraries/botframework-connector/botframework/connector/about.py @@ -5,7 +5,7 @@ __title__ = "botframework-connector" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 3c789ac2c..0632606a7 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -1,5 +1,5 @@ msrest==0.7.* -botbuilder-schema==4.16.0 +botbuilder-schema==4.17.0 requests==2.32.0 PyJWT==2.4.0 cryptography==42.0.4 diff --git a/libraries/botframework-connector/setup.py b/libraries/botframework-connector/setup.py index ad269fe60..8a99b3e19 100644 --- a/libraries/botframework-connector/setup.py +++ b/libraries/botframework-connector/setup.py @@ -5,12 +5,12 @@ from setuptools import setup NAME = "botframework-connector" -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" REQUIRES = [ "msrest==0.7.*", # "requests>=2.23.0,<2.26", "PyJWT>=2.4.0", - "botbuilder-schema==4.16.0", + "botbuilder-schema==4.17.0", "msal>=1.29.0", ] diff --git a/libraries/botframework-streaming/botframework/streaming/about.py b/libraries/botframework-streaming/botframework/streaming/about.py index 9e4ba1d54..834a4a9a6 100644 --- a/libraries/botframework-streaming/botframework/streaming/about.py +++ b/libraries/botframework-streaming/botframework/streaming/about.py @@ -5,7 +5,7 @@ __title__ = "botframework-streaming" __version__ = ( - os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" + os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" ) __uri__ = "https://www.github.com/Microsoft/botbuilder-python" __author__ = "Microsoft" diff --git a/libraries/botframework-streaming/requirements.txt b/libraries/botframework-streaming/requirements.txt index db9a49e2b..d951e779a 100644 --- a/libraries/botframework-streaming/requirements.txt +++ b/libraries/botframework-streaming/requirements.txt @@ -1,3 +1,3 @@ msrest==0.7.* -botframework-connector>=4.16.0 -botbuilder-schema>=4.16.0 \ No newline at end of file +botframework-connector>=4.17.0 +botbuilder-schema>=4.17.0 \ No newline at end of file diff --git a/libraries/botframework-streaming/setup.py b/libraries/botframework-streaming/setup.py index 0a45151fc..76c1e9549 100644 --- a/libraries/botframework-streaming/setup.py +++ b/libraries/botframework-streaming/setup.py @@ -4,10 +4,10 @@ import os from setuptools import setup -VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.16.0" +VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" REQUIRES = [ - "botbuilder-schema==4.16.0", - "botframework-connector==4.16.0", + "botbuilder-schema==4.17.0", + "botframework-connector==4.17.0", ] root = os.path.abspath(os.path.dirname(__file__)) From d7b20cbbf5e10236fc2a2769b3d8cb1c960dfc54 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:33:45 +0530 Subject: [PATCH 060/102] Remove colon (':') from keywords such as "Parameters" and "Remarks" in comments (#2130) Co-authored-by: tracyboehrer --- .../botbuilder-ai/botbuilder/ai/qna/models/join_operator.py | 2 +- .../botbuilder/ai/qna/models/qna_response_context.py | 2 +- .../botbuilder/ai/qna/models/qnamaker_trace_info.py | 2 +- .../botbuilder-ai/botbuilder/ai/qna/models/query_results.py | 2 +- .../botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py | 4 ++-- .../botbuilder/ai/qna/utils/active_learning_utils.py | 2 +- .../botbuilder/ai/qna/utils/generate_answer_utils.py | 2 +- .../botbuilder/ai/qna/utils/http_request_utils.py | 4 ++-- .../botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py | 2 +- .../botbuilder/dialogs/choices/choice_factory.py | 6 +++--- .../botbuilder/dialogs/choices/choice_recognizers.py | 4 ++-- .../botbuilder/dialogs/choices/find_choices_options.py | 2 +- .../botbuilder/dialogs/choices/find_values_options.py | 2 +- .../botbuilder/dialogs/choices/found_choice.py | 2 +- .../botbuilder/dialogs/choices/found_value.py | 2 +- .../botbuilder/dialogs/choices/model_result.py | 2 +- .../botbuilder/dialogs/choices/sorted_value.py | 2 +- .../botbuilder-dialogs/botbuilder/dialogs/choices/token.py | 2 +- .../botbuilder/dialogs/choices/tokenizer.py | 2 +- 19 files changed, 24 insertions(+), 24 deletions(-) diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/models/join_operator.py b/libraries/botbuilder-ai/botbuilder/ai/qna/models/join_operator.py index a454afa81..67b7ba1bd 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/models/join_operator.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/models/join_operator.py @@ -8,7 +8,7 @@ class JoinOperator(str, Enum): """ Join Operator for Strict Filters. - remarks: + remarks -------- For example, when using multiple filters in a query, if you want results that have metadata that matches all filters, then use `AND` operator. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/models/qna_response_context.py b/libraries/botbuilder-ai/botbuilder/ai/qna/models/qna_response_context.py index bf68bb213..643180779 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/models/qna_response_context.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/models/qna_response_context.py @@ -17,7 +17,7 @@ class QnAResponseContext(Model): def __init__(self, **kwargs): """ - Parameters: + Parameters ----------- is_context_only: Whether this prompt is context only. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/models/qnamaker_trace_info.py b/libraries/botbuilder-ai/botbuilder/ai/qna/models/qnamaker_trace_info.py index 16bcc7f8e..1d15a93ed 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/models/qnamaker_trace_info.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/models/qnamaker_trace_info.py @@ -27,7 +27,7 @@ def __init__( ranker_type: str = RankerTypes.DEFAULT, ): """ - Parameters: + Parameters ----------- message: Message which instigated the query to QnA Maker. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/models/query_results.py b/libraries/botbuilder-ai/botbuilder/ai/qna/models/query_results.py index 450f47067..46d2cfa93 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/models/query_results.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/models/query_results.py @@ -18,7 +18,7 @@ def __init__( self, answers: List[QueryResult], active_learning_enabled: bool = None, **kwargs ): """ - Parameters: + Parameters ----------- answers: The answers for a user query. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py index af4a4ad1c..72dfe4e9d 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py @@ -10,7 +10,7 @@ class QnAMakerOptions: """ Defines options used to configure a `QnAMaker` instance. - remarks: + remarks -------- All parameters are optional. """ @@ -28,7 +28,7 @@ def __init__( strict_filters_join_operator: str = JoinOperator.AND, ): """ - Parameters: + Parameters ----------- score_threshold (float): The minimum score threshold, used to filter returned results. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/active_learning_utils.py b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/active_learning_utils.py index 5a63666a8..3b549ce1c 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/active_learning_utils.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/active_learning_utils.py @@ -22,7 +22,7 @@ def get_low_score_variation( """ Returns a list of QnA search results, which have low score variation. - Parameters: + Parameters ----------- qna_serach_results: A list of QnA QueryResults returned from the QnA GenerateAnswer API call. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/generate_answer_utils.py b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/generate_answer_utils.py index 1f335f9e6..2a8209ec5 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/generate_answer_utils.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/generate_answer_utils.py @@ -41,7 +41,7 @@ def __init__( http_client: ClientSession, ): """ - Parameters: + Parameters ----------- telemetry_client: Telemetry client. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/http_request_utils.py b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/http_request_utils.py index baca83ae0..8251471c7 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/http_request_utils.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/http_request_utils.py @@ -16,7 +16,7 @@ class HttpRequestUtils: """HTTP request utils class. - Parameters: + Parameters ----------- http_client: Client to make HTTP requests with. Default client used in the SDK is `aiohttp.ClientSession`. @@ -35,7 +35,7 @@ async def execute_http_request( """ Execute HTTP request. - Parameters: + Parameters ----------- request_url: HTTP request URL. diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py index b47eca8f9..c803d79eb 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/utils/train_utils.py @@ -17,7 +17,7 @@ def __init__(self, endpoint: QnAMakerEndpoint, http_client: ClientSession): """ Initializes a new instance for active learning train utils. - Parameters: + Parameters ----------- endpoint: QnA Maker Endpoint of the knowledge base to query. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py index 52bf778b3..0e5edd8e1 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py @@ -26,7 +26,7 @@ def for_channel( Creates a message activity that includes a list of choices formatted based on the capabilities of a given channel. - Parameters: + Parameters ---------- channel_id: A channel ID. choices: List of choices to render @@ -81,7 +81,7 @@ def inline( """ Creates a message activity that includes a list of choices formatted as an inline list. - Parameters: + Parameters ---------- choices: The list of choices to render. text: (Optional) The text of the message to send. @@ -140,7 +140,7 @@ def list_style( """ Creates a message activity that includes a list of choices formatted as a numbered or bulleted list. - Parameters: + Parameters ---------- choices: The list of choices to render. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_recognizers.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_recognizers.py index ec4b226b7..4bdec08c3 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_recognizers.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_recognizers.py @@ -34,7 +34,7 @@ def recognize_choices( - By 1's based ordinal position. - By 1's based index position. - Parameters: + Parameters ----------- utterance: The input. @@ -43,7 +43,7 @@ def recognize_choices( options: (Optional) Options to control the recognition strategy. - Returns: + Returns -------- A list of found choices, sorted by most relevant first. """ diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_choices_options.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_choices_options.py index 4d7c15471..750ab79c6 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_choices_options.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_choices_options.py @@ -16,7 +16,7 @@ def __init__( **kwargs, ): """ - Parameters: + Parameters ----------- no_value: (Optional) If `True`, the choices `value` field will NOT be search over. Defaults to `False`. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_values_options.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_values_options.py index 62ac0acfa..5af0614db 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_values_options.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/find_values_options.py @@ -17,7 +17,7 @@ def __init__( tokenizer: Callable[[str, str], List[Token]] = None, ): """ - Parameters: + Parameters ---------- allow_partial_matches: (Optional) If `True`, then only some of the tokens in a value need to exist to diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_choice.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_choice.py index b32fd09a3..c179eab4c 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_choice.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_choice.py @@ -7,7 +7,7 @@ class FoundChoice: def __init__(self, value: str, index: int, score: float, synonym: str = None): """ - Parameters: + Parameters ---------- value: The value of the choice that was matched. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_value.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_value.py index d25b19052..48e236dc8 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_value.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/found_value.py @@ -7,7 +7,7 @@ class FoundValue: def __init__(self, value: str, index: int, score: float): """ - Parameters: + Parameters ---------- value: The value that was matched. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/model_result.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/model_result.py index d2ec65a1e..b3dbe5beb 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/model_result.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/model_result.py @@ -9,7 +9,7 @@ def __init__( self, text: str, start: int, end: int, type_name: str, resolution: object ): """ - Parameters: + Parameters ---------- text: Substring of the utterance that was recognized. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/sorted_value.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/sorted_value.py index 6a4a2123f..f03c38aef 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/sorted_value.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/sorted_value.py @@ -7,7 +7,7 @@ class SortedValue: def __init__(self, value: str, index: int): """ - Parameters: + Parameters ----------- value: The value that will be sorted. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/token.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/token.py index 63418b322..1b7e028f2 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/token.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/token.py @@ -7,7 +7,7 @@ class Token: def __init__(self, start: int, end: int, text: str, normalized: str): """ - Parameters: + Parameters ---------- start: The index of the first character of the token within the outer input string. diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/tokenizer.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/tokenizer.py index 80d805f14..59e796c84 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/tokenizer.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/tokenizer.py @@ -75,7 +75,7 @@ def _is_breaking_char(code_point) -> bool: @staticmethod def _is_between(value: int, from_val: int, to_val: int) -> bool: """ - Parameters: + Parameters ----------- value: number value From acfa77583e01ac9d19fa7d9596911ddbf7b1064a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 2 Jul 2024 15:12:25 -0500 Subject: [PATCH 061/102] Added CertificateServiceClientCredentialsFactory (#2132) * Added CertificateServiceClientCredentialsFactory * Added Gov and Private cloud support to CertificateServiceClientCredentialsFactory * Corrected _CertificatePrivateCloudAppCredentials * Fixed _CertificatePrivateCloudAppCredentials creation * Added CertificateServiceClientCredentialsFactoryTests * CertificateServiceClientCredentialsFactoryTests formatting * Corrected CertificateAppCredentials scopes * CertificateAppCredentials formatting --------- Co-authored-by: Tracy Boehrer --- .../botframework/connector/auth/__init__.py | 2 + .../auth/certificate_app_credentials.py | 14 +- .../certificate_government_app_credentials.py | 51 +++++++ ...icate_service_client_credential_factory.py | 129 ++++++++++++++++++ ...icate_service_client_credential_factory.py | 73 ++++++++++ 5 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 libraries/botframework-connector/botframework/connector/auth/certificate_government_app_credentials.py create mode 100644 libraries/botframework-connector/botframework/connector/auth/certificate_service_client_credential_factory.py create mode 100644 libraries/botframework-connector/tests/test_certificate_service_client_credential_factory.py diff --git a/libraries/botframework-connector/botframework/connector/auth/__init__.py b/libraries/botframework-connector/botframework/connector/auth/__init__.py index 8747a03c8..d58dcf5fa 100644 --- a/libraries/botframework-connector/botframework/connector/auth/__init__.py +++ b/libraries/botframework-connector/botframework/connector/auth/__init__.py @@ -17,6 +17,8 @@ from .microsoft_app_credentials import * from .microsoft_government_app_credentials import * from .certificate_app_credentials import * +from .certificate_government_app_credentials import * +from .certificate_service_client_credential_factory import * from .claims_identity import * from .jwt_token_validation import * from .credential_provider import * diff --git a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py index b39511d82..89dbe882d 100644 --- a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py @@ -44,7 +44,6 @@ def __init__( oauth_scope=oauth_scope, ) - self.scopes = [self.oauth_scope] self.app = None self.certificate_thumbprint = certificate_thumbprint self.certificate_private_key = certificate_private_key @@ -56,17 +55,18 @@ def get_access_token(self, force_refresh: bool = False) -> str: :return: The access token for the given certificate. """ + scope = self.oauth_scope + if not scope.endswith("/.default"): + scope += "/.default" + scopes = [scope] + # Firstly, looks up a token from cache # Since we are looking for token for the current app, NOT for an end user, # notice we give account parameter as None. - auth_token = self.__get_msal_app().acquire_token_silent( - self.scopes, account=None - ) + auth_token = self.__get_msal_app().acquire_token_silent(scopes, account=None) if not auth_token: # No suitable token exists in cache. Let's get a new one from AAD. - auth_token = self.__get_msal_app().acquire_token_for_client( - scopes=self.scopes - ) + auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) return auth_token["access_token"] def __get_msal_app(self): diff --git a/libraries/botframework-connector/botframework/connector/auth/certificate_government_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/certificate_government_app_credentials.py new file mode 100644 index 000000000..b2883cfa1 --- /dev/null +++ b/libraries/botframework-connector/botframework/connector/auth/certificate_government_app_credentials.py @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .certificate_app_credentials import CertificateAppCredentials +from .government_constants import GovernmentConstants + + +class CertificateGovernmentAppCredentials(CertificateAppCredentials): + """ + GovernmentAppCredentials implementation using a certificate. + """ + + def __init__( + self, + app_id: str, + certificate_thumbprint: str, + certificate_private_key: str, + channel_auth_tenant: str = None, + oauth_scope: str = None, + certificate_public: str = None, + ): + """ + AppCredentials implementation using a certificate. + + :param app_id: + :param certificate_thumbprint: + :param certificate_private_key: + :param channel_auth_tenant: + :param oauth_scope: + :param certificate_public: public_certificate (optional) is public key certificate which will be sent + through ‘x5c’ JWT header only for subject name and issuer authentication to support cert auto rolls. + """ + + # super will set proper scope and endpoint. + super().__init__( + app_id=app_id, + channel_auth_tenant=channel_auth_tenant, + oauth_scope=oauth_scope, + certificate_thumbprint=certificate_thumbprint, + certificate_private_key=certificate_private_key, + certificate_public=certificate_public, + ) + + 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/certificate_service_client_credential_factory.py b/libraries/botframework-connector/botframework/connector/auth/certificate_service_client_credential_factory.py new file mode 100644 index 000000000..7a71c28bd --- /dev/null +++ b/libraries/botframework-connector/botframework/connector/auth/certificate_service_client_credential_factory.py @@ -0,0 +1,129 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from logging import Logger + +from msrest.authentication import Authentication + +from .authentication_constants import AuthenticationConstants +from .government_constants import GovernmentConstants +from .certificate_app_credentials import CertificateAppCredentials +from .certificate_government_app_credentials import CertificateGovernmentAppCredentials +from .microsoft_app_credentials import MicrosoftAppCredentials +from .service_client_credentials_factory import ServiceClientCredentialsFactory + + +class CertificateServiceClientCredentialsFactory(ServiceClientCredentialsFactory): + def __init__( + self, + certificate_thumbprint: str, + certificate_private_key: str, + app_id: str, + tenant_id: str = None, + certificate_public: str = None, + *, + logger: Logger = None + ) -> None: + """ + CertificateServiceClientCredentialsFactory implementation using a certificate. + + :param certificate_thumbprint: + :param certificate_private_key: + :param app_id: + :param tenant_id: + :param certificate_public: public_certificate (optional) is public key certificate which will be sent + through ‘x5c’ JWT header only for subject name and issuer authentication to support cert auto rolls. + """ + + self.certificate_thumbprint = certificate_thumbprint + self.certificate_private_key = certificate_private_key + self.app_id = app_id + self.tenant_id = tenant_id + self.certificate_public = certificate_public + self._logger = logger + + async def is_valid_app_id(self, app_id: str) -> bool: + return app_id == self.app_id + + async def is_authentication_disabled(self) -> bool: + return not self.app_id + + async def create_credentials( + self, + app_id: str, + oauth_scope: str, + login_endpoint: str, + validate_authority: bool, + ) -> Authentication: + if await self.is_authentication_disabled(): + return MicrosoftAppCredentials.empty() + + if not await self.is_valid_app_id(app_id): + raise Exception("Invalid app_id") + + normalized_endpoint = login_endpoint.lower() if login_endpoint else "" + + if normalized_endpoint.startswith( + AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + ): + credentials = CertificateAppCredentials( + app_id, + self.certificate_thumbprint, + self.certificate_private_key, + self.tenant_id, + oauth_scope, + self.certificate_public, + ) + elif normalized_endpoint.startswith( + GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + ): + credentials = CertificateGovernmentAppCredentials( + app_id, + self.certificate_thumbprint, + self.certificate_private_key, + self.tenant_id, + oauth_scope, + self.certificate_public, + ) + else: + credentials = _CertificatePrivateCloudAppCredentials( + app_id, + self.certificate_thumbprint, + self.certificate_private_key, + self.tenant_id, + oauth_scope, + self.certificate_public, + login_endpoint, + validate_authority, + ) + + return credentials + + +class _CertificatePrivateCloudAppCredentials(CertificateAppCredentials): + def __init__( + self, + app_id: str, + certificate_thumbprint: str, + certificate_private_key: str, + channel_auth_tenant: str, + oauth_scope: str, + certificate_public: str, + oauth_endpoint: str, + validate_authority: bool, + ): + super().__init__( + app_id, + certificate_thumbprint, + certificate_private_key, + channel_auth_tenant, + oauth_scope, + certificate_public, + ) + + self.oauth_endpoint = oauth_endpoint + self._validate_authority = validate_authority + + @property + def validate_authority(self): + return self._validate_authority diff --git a/libraries/botframework-connector/tests/test_certificate_service_client_credential_factory.py b/libraries/botframework-connector/tests/test_certificate_service_client_credential_factory.py new file mode 100644 index 000000000..558397c9f --- /dev/null +++ b/libraries/botframework-connector/tests/test_certificate_service_client_credential_factory.py @@ -0,0 +1,73 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botframework.connector.auth import ( + AppCredentials, + AuthenticationConstants, + GovernmentConstants, + CertificateServiceClientCredentialsFactory, + CertificateAppCredentials, + CertificateGovernmentAppCredentials, +) + + +class CertificateServiceClientCredentialsFactoryTests(aiounittest.AsyncTestCase): + test_appid = "test_appid" + test_tenant_id = "test_tenant_id" + test_audience = "test_audience" + login_endpoint = AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + gov_login_endpoint = GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + private_login_endpoint = "https://login.privatecloud.com" + + async def test_can_create_public_credentials(self): + factory = CertificateServiceClientCredentialsFactory( + app_id=CertificateServiceClientCredentialsFactoryTests.test_appid, + certificate_thumbprint="thumbprint", + certificate_private_key="private_key", + ) + + credentials = await factory.create_credentials( + CertificateServiceClientCredentialsFactoryTests.test_appid, + CertificateServiceClientCredentialsFactoryTests.test_audience, + CertificateServiceClientCredentialsFactoryTests.login_endpoint, + True, + ) + + assert isinstance(credentials, CertificateAppCredentials) + + async def test_can_create_gov_credentials(self): + factory = CertificateServiceClientCredentialsFactory( + app_id=CertificateServiceClientCredentialsFactoryTests.test_appid, + certificate_thumbprint="thumbprint", + certificate_private_key="private_key", + ) + + credentials = await factory.create_credentials( + CertificateServiceClientCredentialsFactoryTests.test_appid, + CertificateServiceClientCredentialsFactoryTests.test_audience, + CertificateServiceClientCredentialsFactoryTests.gov_login_endpoint, + True, + ) + + assert isinstance(credentials, CertificateGovernmentAppCredentials) + + async def test_can_create_private_credentials(self): + factory = CertificateServiceClientCredentialsFactory( + app_id=CertificateServiceClientCredentialsFactoryTests.test_appid, + certificate_thumbprint="thumbprint", + certificate_private_key="private_key", + ) + + credentials = await factory.create_credentials( + CertificateServiceClientCredentialsFactoryTests.test_appid, + CertificateServiceClientCredentialsFactoryTests.test_audience, + CertificateServiceClientCredentialsFactoryTests.private_login_endpoint, + True, + ) + + assert isinstance(credentials, CertificateAppCredentials) + assert ( + credentials.oauth_endpoint + == CertificateServiceClientCredentialsFactoryTests.private_login_endpoint + ) From a7fd4399fd1a527269ede473c0fb1c4b4a4c6c73 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 8 Jul 2024 12:48:26 -0500 Subject: [PATCH 062/102] Fix for ConfigurationServiceClientCredentialFactory to allow auth disabled scenario (#2135) Co-authored-by: Tracy Boehrer --- .../configuration_service_client_credential_factory.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 6379e16b6..79a6437b7 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 @@ -71,13 +71,7 @@ def __init__(self, configuration: Any, *, logger: Logger = None) -> None: # Default to MultiTenant else: - 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" - ) - + # Specifically not checking for appId or password to allow auth disabled scenario self._inner = PasswordServiceClientCredentialFactory( app_id, app_password, None, logger=logger ) From cea5ccc784b790fd8b10299570daa02652d73922 Mon Sep 17 00:00:00 2001 From: Linda Date: Tue, 16 Jul 2024 13:23:22 -0700 Subject: [PATCH 063/102] Raise PermissionError if access_token not available on auth_token object (#2138) --- .../connector/auth/microsoft_app_credentials.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 532071667..6056fec5e 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -52,7 +52,12 @@ def get_access_token(self, force_refresh: bool = False) -> str: if not auth_token: # No suitable token exists in cache. Let's get a new one from AAD. auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) - return auth_token["access_token"] + if "access_token" in auth_token: + return auth_token["access_token"] + else: + error = auth_token["error"] if "error" in auth_token else "Unknown error" + error_description = auth_token["error_description"] if "error_description" in auth_token else "Unknown error description" + raise PermissionError(f"Failed to get access token with error: {error}, error_description: {error_description}") def __get_msal_app(self): if not self.app: From b8dd2052fd963f7e1dcca1609298239eca2781ff Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:15:19 +0530 Subject: [PATCH 064/102] Port: [Teams] Add support for meeting participants added/removed events (#2137) * [Teams] Add support for meeting participants added/removed events * test case issue and code format fix * code format fix for microsoft_app_credentials.py * removing unnecessary else --- .../core/teams/teams_activity_handler.py | 41 +++++++++++ .../teams/test_teams_activity_handler.py | 71 +++++++++++++++++++ .../botbuilder/schema/teams/__init__.py | 6 ++ .../botbuilder/schema/teams/_models_py3.py | 62 ++++++++++++++++ .../auth/microsoft_app_credentials.py | 13 ++-- 5 files changed, 189 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 2e5774cc2..33b4e419c 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -27,6 +27,7 @@ TaskModuleResponse, TabRequest, TabSubmit, + MeetingParticipantsEventDetails, ) from botframework.connector import Channels from ..serializer_helper import deserializer_helper @@ -913,6 +914,20 @@ async def on_event_activity(self, turn_context: TurnContext): return await self.on_teams_meeting_end_event( turn_context.activity.value, turn_context ) + if ( + turn_context.activity.name + == "application/vnd.microsoft.meetingParticipantJoin" + ): + return await self.on_teams_meeting_participants_join_event( + turn_context.activity.value, turn_context + ) + if ( + turn_context.activity.name + == "application/vnd.microsoft.meetingParticipantLeave" + ): + return await self.on_teams_meeting_participants_leave_event( + turn_context.activity.value, turn_context + ) return await super().on_event_activity(turn_context) @@ -941,3 +956,29 @@ async def on_teams_meeting_end_event( :returns: A task that represents the work queued to execute. """ return + + async def on_teams_meeting_participants_join_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): # pylint: disable=unused-argument + """ + Override this in a derived class to provide logic for when meeting participants are added. + + :param meeting: The details of the meeting. + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return + + async def on_teams_meeting_participants_leave_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): # pylint: disable=unused-argument + """ + Override this in a derived class to provide logic for when meeting participants are removed. + + :param meeting: The details of the meeting. + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 32e9f2edb..390df6191 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -32,6 +32,7 @@ TabRequest, TabSubmit, TabContext, + MeetingParticipantsEventDetails, ) from botframework.connector import Channels from simple_adapter import SimpleAdapter @@ -333,6 +334,22 @@ async def on_teams_meeting_end_event( turn_context.activity.value, turn_context ) + async def on_teams_meeting_participants_join_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): + self.record.append("on_teams_meeting_participants_join_event") + return await super().on_teams_meeting_participants_join_event( + turn_context.activity.value, turn_context + ) + + async def on_teams_meeting_participants_leave_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): + self.record.append("on_teams_meeting_participants_leave_event") + return await super().on_teams_meeting_participants_leave_event( + turn_context.activity.value, turn_context + ) + class NotImplementedAdapter(BotAdapter): async def delete_activity( @@ -1157,3 +1174,57 @@ async def test_on_teams_meeting_end_event(self): assert len(bot.record) == 2 assert bot.record[0] == "on_event_activity" assert bot.record[1] == "on_teams_meeting_end_event" + + async def test_on_teams_meeting_participants_join_event(self): + # arrange + activity = Activity( + type=ActivityTypes.event, + channel_id=Channels.ms_teams, + name="application/vnd.microsoft.meetingParticipantJoin", + value={ + "members": [ + { + "user": {"id": "123", "name": "name"}, + "meeting": {"role": "role", "in_meeting": True}, + } + ], + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_event_activity" + assert bot.record[1] == "on_teams_meeting_participants_join_event" + + async def test_on_teams_meeting_participants_leave_event(self): + # arrange + activity = Activity( + type=ActivityTypes.event, + channel_id=Channels.ms_teams, + name="application/vnd.microsoft.meetingParticipantLeave", + value={ + "members": [ + { + "user": {"id": "id", "name": "name"}, + "meeting": {"role": "role", "in_meeting": True}, + } + ], + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_event_activity" + assert bot.record[1] == "on_teams_meeting_participants_leave_event" diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 7824e4571..55901f7a4 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -77,6 +77,9 @@ from ._models_py3 import TabSubmitData from ._models_py3 import TabSuggestedActions from ._models_py3 import TaskModuleCardResponse +from ._models_py3 import UserMeetingDetails +from ._models_py3 import TeamsMeetingMember +from ._models_py3 import MeetingParticipantsEventDetails __all__ = [ "AppBasedLinkQuery", @@ -155,4 +158,7 @@ "TabSubmitData", "TabSuggestedActions", "TaskModuleCardResponse", + "UserMeetingDetails", + "TeamsMeetingMember", + "MeetingParticipantsEventDetails", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index bb860c8da..260442cf8 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2506,3 +2506,65 @@ class MeetingEndEventDetails(MeetingDetailsBase): def __init__(self, *, end_time: str = None, **kwargs): super(MeetingEndEventDetails, self).__init__(**kwargs) self.end_time = end_time + + +class UserMeetingDetails(Model): + """Specific details of a user in a Teams meeting. + + :param role: Role of the participant in the current meeting. + :type role: str + :param in_meeting: True, if the participant is in the meeting. + :type in_meeting: bool + """ + + _attribute_map = { + "role": {"key": "role", "type": "str"}, + "in_meeting": {"key": "inMeeting", "type": "bool"}, + } + + def __init__(self, *, role: str = None, in_meeting: bool = None, **kwargs) -> None: + super(UserMeetingDetails, self).__init__(**kwargs) + self.in_meeting = in_meeting + self.role = role + + +class TeamsMeetingMember(Model): + """Data about the meeting participants. + + :param user: The channel user data. + :type user: TeamsChannelAccount + :param meeting: The user meeting details. + :type meeting: UserMeetingDetails + """ + + _attribute_map = { + "user": {"key": "user", "type": "TeamsChannelAccount"}, + "meeting": {"key": "meeting", "type": "UserMeetingDetails"}, + } + + def __init__( + self, + *, + user: TeamsChannelAccount = None, + meeting: UserMeetingDetails = None, + **kwargs + ) -> None: + super(TeamsMeetingMember, self).__init__(**kwargs) + self.user = user + self.meeting = meeting + + +class MeetingParticipantsEventDetails(Model): + """Data about the meeting participants. + + :param members: The members involved in the meeting event. + :type members: list[~botframework.connector.models.TeamsMeetingMember] + """ + + _attribute_map = { + "conversations": {"key": "members", "type": "[TeamsMeetingMember]"}, + } + + def __init__(self, *, members: List[TeamsMeetingMember] = None, **kwargs) -> None: + super(MeetingParticipantsEventDetails, self).__init__(**kwargs) + self.members = members 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 6056fec5e..523977b08 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -54,10 +54,15 @@ def get_access_token(self, force_refresh: bool = False) -> str: auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) if "access_token" in auth_token: return auth_token["access_token"] - else: - error = auth_token["error"] if "error" in auth_token else "Unknown error" - error_description = auth_token["error_description"] if "error_description" in auth_token else "Unknown error description" - raise PermissionError(f"Failed to get access token with error: {error}, error_description: {error_description}") + error = auth_token["error"] if "error" in auth_token else "Unknown error" + error_description = ( + auth_token["error_description"] + if "error_description" in auth_token + else "Unknown error description" + ) + raise PermissionError( + f"Failed to get access token with error: {error}, error_description: {error_description}" + ) def __get_msal_app(self): if not self.app: From a2cccb32d1d23639f304288b6d7d970bb1bbd3ba Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:50:10 +0530 Subject: [PATCH 065/102] Add conversation id header in skill requests (#2141) --- .../botbuilder/integration/aiohttp/bot_framework_http_client.py | 1 + .../aio/operations_async/_conversations_operations_async.py | 1 + .../connector/operations/_conversations_operations.py | 1 + 3 files changed, 3 insertions(+) diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py index b09b3ac93..c57c042c2 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py @@ -116,6 +116,7 @@ async def _post_content( ) -> Tuple[int, object]: headers_dict = { "Content-type": "application/json; charset=utf-8", + "x-ms-conversation-id": activity.conversation.id, } if token: headers_dict.update( diff --git a/libraries/botframework-connector/botframework/connector/aio/operations_async/_conversations_operations_async.py b/libraries/botframework-connector/botframework/connector/aio/operations_async/_conversations_operations_async.py index e6416e42d..553248342 100644 --- a/libraries/botframework-connector/botframework/connector/aio/operations_async/_conversations_operations_async.py +++ b/libraries/botframework-connector/botframework/connector/aio/operations_async/_conversations_operations_async.py @@ -510,6 +510,7 @@ async def reply_to_activity( header_parameters = {} header_parameters["Accept"] = "application/json" header_parameters["Content-Type"] = "application/json; charset=utf-8" + header_parameters["x-ms-conversation-id"] = conversation_id if custom_headers: header_parameters.update(custom_headers) diff --git a/libraries/botframework-connector/botframework/connector/operations/_conversations_operations.py b/libraries/botframework-connector/botframework/connector/operations/_conversations_operations.py index 6834f7a28..48d3c23fc 100644 --- a/libraries/botframework-connector/botframework/connector/operations/_conversations_operations.py +++ b/libraries/botframework-connector/botframework/connector/operations/_conversations_operations.py @@ -495,6 +495,7 @@ def reply_to_activity( header_parameters = {} header_parameters["Accept"] = "application/json" header_parameters["Content-Type"] = "application/json; charset=utf-8" + header_parameters["x-ms-conversation-id"] = conversation_id if custom_headers: header_parameters.update(custom_headers) From 9a70940b58cfe7d2a24cc24e114ee749fca37052 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:57:13 +0530 Subject: [PATCH 066/102] Adding directlinespeech & test channels in channels.py (#2143) Co-authored-by: tracyboehrer --- .../botbuilder/adapters/slack/slack_helper.py | 7 ++++--- .../botbuilder/core/adapters/test_adapter.py | 5 +++-- .../tests/test_bot_framework_adapter.py | 13 +++++++------ .../tests/test_conversation_state.py | 11 ++++++++--- .../tests/test_memory_transcript_store.py | 5 +++-- .../tests/test_private_conversation_state.py | 3 ++- .../tests/test_telemetry_middleware.py | 2 +- .../botbuilder-core/tests/test_test_adapter.py | 9 +++++---- libraries/botbuilder-core/tests/test_user_state.py | 7 +++++-- .../botbuilder/dialogs/choices/channel.py | 2 ++ .../tests/choices/test_channel.py | 2 ++ .../tests/memory/scopes/test_memory_scopes.py | 3 ++- .../botbuilder-dialogs/tests/test_dialog_manager.py | 3 ++- .../botframework/connector/channels.py | 6 ++++++ .../tests/test_attachments.py | 4 ++-- .../tests/test_attachments_async.py | 3 ++- .../tests/test_conversations.py | 4 ++-- .../tests/test_conversations_async.py | 3 ++- 18 files changed, 60 insertions(+), 32 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py index 35f720c8d..c40dd21e9 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py @@ -15,6 +15,7 @@ ChannelAccount, ActivityTypes, ) +from botframework.connector import Channels from .slack_message import SlackMessage from .slack_client import SlackClient @@ -125,7 +126,7 @@ def payload_to_activity(payload: SlackPayload) -> Activity: raise Exception("payload is required") activity = Activity( - channel_id="slack", + channel_id=Channels.slack, conversation=ConversationAccount(id=payload.channel["id"], properties={}), from_property=ChannelAccount( id=( @@ -178,7 +179,7 @@ async def event_to_activity(event: SlackEvent, client: SlackClient) -> Activity: activity = Activity( id=event.event_ts, - channel_id="slack", + channel_id=Channels.slack, conversation=ConversationAccount( id=event.channel if event.channel else event.channel_id, properties={} ), @@ -235,7 +236,7 @@ async def command_to_activity( activity = Activity( id=body.trigger_id, - channel_id="slack", + channel_id=Channels.slack, conversation=ConversationAccount(id=body.channel_id, properties={}), from_property=ChannelAccount(id=body.user_id), recipient=ChannelAccount(id=None), diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 56f122344..79070d732 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -31,6 +31,7 @@ from ..bot_adapter import BotAdapter from ..turn_context import TurnContext from ..oauth.extended_user_token_provider import ExtendedUserTokenProvider +from botframework.connector import Channels class UserToken: @@ -121,7 +122,7 @@ def __init__( template_or_conversation if isinstance(template_or_conversation, Activity) else Activity( - channel_id="test", + channel_id=Channels.test, service_url="https://test.com", from_property=ChannelAccount(id="User1", name="user"), recipient=ChannelAccount(id="bot", name="Bot"), @@ -308,7 +309,7 @@ def create_conversation_reference( name: str, user: str = "User1", bot: str = "Bot" ) -> ConversationReference: return ConversationReference( - channel_id="test", + channel_id=Channels.test, service_url="https://test.com", conversation=ConversationAccount( is_group=False, diff --git a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py index 7e9268ee7..ee8faa773 100644 --- a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py +++ b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py @@ -27,6 +27,7 @@ TokenExchangeInvokeRequest, TokenExchangeInvokeResponse, ) +from botframework.connector import Channels from botframework.connector.token_api.models import ( TokenExchangeRequest, TokenResponse as ConnectorTokenResponse, @@ -44,7 +45,7 @@ REFERENCE = ConversationReference( activity_id="1234", - channel_id="test", + channel_id=Channels.test, locale="en-uS", # Intentionally oddly-cased to check that it isn't defaulted somewhere, but tests stay in English service_url="https://example.org/channel", user=ChannelAccount(id="user", name="User Name"), @@ -305,7 +306,7 @@ async def test_should_migrate_tenant_id_for_msteams(self): is_incoming=True, ) - incoming.channel_id = "msteams" + incoming.channel_id = Channels.ms_teams adapter = AdapterUnderTest() async def aux_func_assert_tenant_id_copied(context): @@ -501,7 +502,7 @@ async def callback(context: TurnContext): sut = BotFrameworkAdapter(settings) await sut.process_activity_with_identity( Activity( - channel_id="emulator", + channel_id=Channels.emulator, service_url=service_url, text="test", ), @@ -549,7 +550,7 @@ async def callback(context: TurnContext): sut = BotFrameworkAdapter(settings) await sut.process_activity_with_identity( Activity( - channel_id="emulator", + channel_id=Channels.emulator, service_url=service_url, text="test", ), @@ -709,7 +710,7 @@ async def callback(context: TurnContext): inbound_activity = Activity( type=ActivityTypes.message, - channel_id="emulator", + channel_id=Channels.emulator, service_url="http://tempuri.org/whatever", delivery_mode=DeliveryModes.expect_replies, text="hello world", @@ -754,7 +755,7 @@ async def callback(context: TurnContext): inbound_activity = Activity( type=ActivityTypes.message, - channel_id="emulator", + channel_id=Channels.emulator, service_url="http://tempuri.org/whatever", delivery_mode=DeliveryModes.normal, text="hello world", diff --git a/libraries/botbuilder-core/tests/test_conversation_state.py b/libraries/botbuilder-core/tests/test_conversation_state.py index 4c4e74c19..79d90ca54 100644 --- a/libraries/botbuilder-core/tests/test_conversation_state.py +++ b/libraries/botbuilder-core/tests/test_conversation_state.py @@ -6,20 +6,25 @@ from botbuilder.core import TurnContext, MemoryStorage, ConversationState from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ConversationAccount +from botframework.connector import Channels RECEIVED_MESSAGE = Activity( type="message", text="received", - channel_id="test", + channel_id=Channels.test, conversation=ConversationAccount(id="convo"), ) MISSING_CHANNEL_ID = Activity( type="message", text="received", conversation=ConversationAccount(id="convo") ) -MISSING_CONVERSATION = Activity(type="message", text="received", channel_id="test") +MISSING_CONVERSATION = Activity( + type="message", + text="received", + channel_id=Channels.test, +) END_OF_CONVERSATION = Activity( type="endOfConversation", - channel_id="test", + channel_id=Channels.test, conversation=ConversationAccount(id="convo"), ) diff --git a/libraries/botbuilder-core/tests/test_memory_transcript_store.py b/libraries/botbuilder-core/tests/test_memory_transcript_store.py index 14b313c92..f7ace436e 100644 --- a/libraries/botbuilder-core/tests/test_memory_transcript_store.py +++ b/libraries/botbuilder-core/tests/test_memory_transcript_store.py @@ -25,6 +25,7 @@ ConversationAccount, ConversationReference, ) +from botframework.connector import Channels # pylint: disable=line-too-long,missing-docstring @@ -98,7 +99,7 @@ def create_activities(self, conversation_id: str, date: datetime, count: int = 5 timestamp=time_stamp, id=str(uuid.uuid4()), text=str(i), - channel_id="test", + channel_id=Channels.test, from_property=ChannelAccount(id=f"User{i}"), conversation=ConversationAccount(id=conversation_id), recipient=ChannelAccount(id="bot1", name="2"), @@ -112,7 +113,7 @@ def create_activities(self, conversation_id: str, date: datetime, count: int = 5 timestamp=date, id=str(uuid.uuid4()), text=str(i), - channel_id="test", + channel_id=Channels.test, from_property=ChannelAccount(id="Bot1", name="2"), conversation=ConversationAccount(id=conversation_id), recipient=ChannelAccount(id=f"User{i}"), diff --git a/libraries/botbuilder-core/tests/test_private_conversation_state.py b/libraries/botbuilder-core/tests/test_private_conversation_state.py index 802a2678b..5fb4507e3 100644 --- a/libraries/botbuilder-core/tests/test_private_conversation_state.py +++ b/libraries/botbuilder-core/tests/test_private_conversation_state.py @@ -6,11 +6,12 @@ from botbuilder.core import MemoryStorage, TurnContext, PrivateConversationState from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ChannelAccount, ConversationAccount +from botframework.connector import Channels RECEIVED_MESSAGE = Activity( text="received", type="message", - channel_id="test", + channel_id=Channels.test, conversation=ConversationAccount(id="convo"), from_property=ChannelAccount(id="user"), ) diff --git a/libraries/botbuilder-core/tests/test_telemetry_middleware.py b/libraries/botbuilder-core/tests/test_telemetry_middleware.py index ee3504d1b..7fdd83109 100644 --- a/libraries/botbuilder-core/tests/test_telemetry_middleware.py +++ b/libraries/botbuilder-core/tests/test_telemetry_middleware.py @@ -40,7 +40,7 @@ async def test_do_not_throw_on_null_from(self): adapter = TestAdapter( template_or_conversation=Activity( - channel_id="test", + channel_id=Channels.test, recipient=ChannelAccount(id="bot", name="Bot"), conversation=ConversationAccount(id=str(uuid.uuid4())), ) diff --git a/libraries/botbuilder-core/tests/test_test_adapter.py b/libraries/botbuilder-core/tests/test_test_adapter.py index 447f74ead..269a5197f 100644 --- a/libraries/botbuilder-core/tests/test_test_adapter.py +++ b/libraries/botbuilder-core/tests/test_test_adapter.py @@ -7,6 +7,7 @@ from botbuilder.core import TurnContext from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ConversationReference, ChannelAccount +from botframework.connector import Channels RECEIVED_MESSAGE = Activity(type="message", text="received") UPDATED_ACTIVITY = Activity(type="message", text="update") @@ -141,7 +142,7 @@ async def logic(context: TurnContext): async def test_get_user_token_returns_null(self): adapter = TestAdapter() activity = Activity( - channel_id="directline", from_property=ChannelAccount(id="testuser") + channel_id=Channels.direct_line, from_property=ChannelAccount(id="testuser") ) turn_context = TurnContext(adapter, activity) @@ -158,7 +159,7 @@ async def test_get_user_token_returns_null(self): async def test_get_user_token_returns_null_with_code(self): adapter = TestAdapter() activity = Activity( - channel_id="directline", from_property=ChannelAccount(id="testuser") + channel_id=Channels.direct_line, from_property=ChannelAccount(id="testuser") ) turn_context = TurnContext(adapter, activity) @@ -180,7 +181,7 @@ async def test_get_user_token_returns_null_with_code(self): async def test_get_user_token_returns_token(self): adapter = TestAdapter() connection_name = "myConnection" - channel_id = "directline" + channel_id = Channels.direct_line user_id = "testUser" token = "abc123" activity = Activity( @@ -207,7 +208,7 @@ async def test_get_user_token_returns_token(self): async def test_get_user_token_returns_token_with_magice_code(self): adapter = TestAdapter() connection_name = "myConnection" - channel_id = "directline" + channel_id = Channels.direct_line user_id = "testUser" token = "abc123" magic_code = "888999" diff --git a/libraries/botbuilder-core/tests/test_user_state.py b/libraries/botbuilder-core/tests/test_user_state.py index a39ee107a..9f7e22679 100644 --- a/libraries/botbuilder-core/tests/test_user_state.py +++ b/libraries/botbuilder-core/tests/test_user_state.py @@ -6,17 +6,20 @@ from botbuilder.core import TurnContext, MemoryStorage, UserState from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ChannelAccount +from botframework.connector import Channels RECEIVED_MESSAGE = Activity( type="message", text="received", - channel_id="test", + channel_id=Channels.test, from_property=ChannelAccount(id="user"), ) MISSING_CHANNEL_ID = Activity( type="message", text="received", from_property=ChannelAccount(id="user") ) -MISSING_FROM_PROPERTY = Activity(type="message", text="received", channel_id="test") +MISSING_FROM_PROPERTY = Activity( + type="message", text="received", channel_id=Channels.test +) class TestUserState(aiounittest.AsyncTestCase): diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/channel.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/channel.py index cd36ac632..aa19a2740 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/channel.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/channel.py @@ -34,6 +34,7 @@ def supports_suggested_actions(channel_id: str, button_cnt: int = 100) -> bool: Channels.telegram: 100, Channels.emulator: 100, Channels.direct_line: 100, + Channels.direct_line_speech: 100, Channels.webchat: 100, } return ( @@ -64,6 +65,7 @@ def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool: Channels.telegram: 100, Channels.emulator: 100, Channels.direct_line: 100, + Channels.direct_line_speech: 100, Channels.webchat: 100, } return ( diff --git a/libraries/botbuilder-dialogs/tests/choices/test_channel.py b/libraries/botbuilder-dialogs/tests/choices/test_channel.py index 269aaae1a..23d26ac4a 100644 --- a/libraries/botbuilder-dialogs/tests/choices/test_channel.py +++ b/libraries/botbuilder-dialogs/tests/choices/test_channel.py @@ -25,6 +25,7 @@ def test_supports_suggested_actions_many(self): (Channels.kik, 21, False), (Channels.emulator, 100, True), (Channels.emulator, 101, False), + (Channels.direct_line_speech, 100, True), ] for channel, button_cnt, expected in supports_suggested_actions_data: @@ -41,6 +42,7 @@ def test_supports_card_actions_many(self): (Channels.slack, 100, True), (Channels.skype, 3, True), (Channels.skype, 5, False), + (Channels.direct_line_speech, 99, True), ] for channel, button_cnt, expected in supports_card_action_data: diff --git a/libraries/botbuilder-dialogs/tests/memory/scopes/test_memory_scopes.py b/libraries/botbuilder-dialogs/tests/memory/scopes/test_memory_scopes.py index 5101c7070..d7b305358 100644 --- a/libraries/botbuilder-dialogs/tests/memory/scopes/test_memory_scopes.py +++ b/libraries/botbuilder-dialogs/tests/memory/scopes/test_memory_scopes.py @@ -32,6 +32,7 @@ ChannelAccount, ConversationAccount, ) +from botframework.connector import Channels class TestDialog(Dialog): @@ -92,7 +93,7 @@ class MemoryScopesTests(aiounittest.AsyncTestCase): begin_message = Activity( text="begin", type=ActivityTypes.message, - channel_id="test", + channel_id=Channels.test, from_property=ChannelAccount(id="user"), recipient=ChannelAccount(id="bot"), conversation=ConversationAccount(id="convo1"), diff --git a/libraries/botbuilder-dialogs/tests/test_dialog_manager.py b/libraries/botbuilder-dialogs/tests/test_dialog_manager.py index 57b264e60..3c5a4b34a 100644 --- a/libraries/botbuilder-dialogs/tests/test_dialog_manager.py +++ b/libraries/botbuilder-dialogs/tests/test_dialog_manager.py @@ -42,6 +42,7 @@ InputHints, ) from botframework.connector.auth import AuthenticationConstants, ClaimsIdentity +from botframework.connector import Channels class SkillFlowTestCase(str, Enum): @@ -103,7 +104,7 @@ async def create_test_flow( user_state = UserState(storage) activity = Activity( - channel_id="test", + channel_id=Channels.test, service_url="https://test.com", from_property=ChannelAccount(id="user1", name="User1"), recipient=ChannelAccount(id="bot", name="Bot"), diff --git a/libraries/botframework-connector/botframework/connector/channels.py b/libraries/botframework-connector/botframework/connector/channels.py index be59cc5f5..569596b9c 100644 --- a/libraries/botframework-connector/botframework/connector/channels.py +++ b/libraries/botframework-connector/botframework/connector/channels.py @@ -18,6 +18,9 @@ class Channels(str, Enum): direct_line = "directline" """Direct Line channel.""" + direct_line_speech = "directlinespeech" + """Direct Line Speech channel.""" + email = "email" """Email channel.""" @@ -54,5 +57,8 @@ class Channels(str, Enum): telegram = "telegram" """Telegram channel.""" + test = "test" + """Test channel.""" + webchat = "webchat" """WebChat channel.""" diff --git a/libraries/botframework-connector/tests/test_attachments.py b/libraries/botframework-connector/tests/test_attachments.py index bd280c840..b6d171250 100644 --- a/libraries/botframework-connector/tests/test_attachments.py +++ b/libraries/botframework-connector/tests/test_attachments.py @@ -8,13 +8,13 @@ import msrest from botbuilder.schema import AttachmentData, ErrorResponseException -from botframework.connector import ConnectorClient +from botframework.connector import ConnectorClient, Channels from botframework.connector.auth import MicrosoftAppCredentials from authentication_stub import MicrosoftTokenAuthenticationStub SERVICE_URL = "https://slack.botframework.com" -CHANNEL_ID = "slack" +CHANNEL_ID = Channels.slack BOT_NAME = "botbuilder-pc-bot" BOT_ID = "B21UTEF8S:T03CWQ0QB" RECIPIENT_ID = "U19KH8EHJ:T03CWQ0QB" diff --git a/libraries/botframework-connector/tests/test_attachments_async.py b/libraries/botframework-connector/tests/test_attachments_async.py index c0f7c9458..fe0434184 100644 --- a/libraries/botframework-connector/tests/test_attachments_async.py +++ b/libraries/botframework-connector/tests/test_attachments_async.py @@ -12,9 +12,10 @@ from botframework.connector.auth import MicrosoftAppCredentials from authentication_stub import MicrosoftTokenAuthenticationStub +from botframework.connector import Channels SERVICE_URL = "https://slack.botframework.com" -CHANNEL_ID = "slack" +CHANNEL_ID = Channels.slack BOT_NAME = "botbuilder-pc-bot" BOT_ID = "B21UTEF8S:T03CWQ0QB" RECIPIENT_ID = "U19KH8EHJ:T03CWQ0QB" diff --git a/libraries/botframework-connector/tests/test_conversations.py b/libraries/botframework-connector/tests/test_conversations.py index c64926643..ea94a247b 100644 --- a/libraries/botframework-connector/tests/test_conversations.py +++ b/libraries/botframework-connector/tests/test_conversations.py @@ -15,13 +15,13 @@ ErrorResponseException, HeroCard, ) -from botframework.connector import ConnectorClient +from botframework.connector import ConnectorClient, Channels from botframework.connector.auth import MicrosoftAppCredentials from authentication_stub import MicrosoftTokenAuthenticationStub SERVICE_URL = "https://slack.botframework.com" -CHANNEL_ID = "slack" +CHANNEL_ID = Channels.slack BOT_NAME = "botbuilder-pc-bot" BOT_ID = "B21UTEF8S:T03CWQ0QB" RECIPIENT_ID = "U19KH8EHJ:T03CWQ0QB" diff --git a/libraries/botframework-connector/tests/test_conversations_async.py b/libraries/botframework-connector/tests/test_conversations_async.py index 074247c82..5e0c8fcc5 100644 --- a/libraries/botframework-connector/tests/test_conversations_async.py +++ b/libraries/botframework-connector/tests/test_conversations_async.py @@ -19,9 +19,10 @@ from botframework.connector.auth import MicrosoftAppCredentials from authentication_stub import MicrosoftTokenAuthenticationStub +from botframework.connector import Channels SERVICE_URL = "https://slack.botframework.com" -CHANNEL_ID = "slack" +CHANNEL_ID = Channels.slack BOT_NAME = "botbuilder-pc-bot" BOT_ID = "B21UTEF8S:T03CWQ0QB" RECIPIENT_ID = "U19KH8EHJ:T03CWQ0QB" From 1099ffdfba5daa8fceae14135339bb8f2e87b9fe Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:46:20 +0530 Subject: [PATCH 067/102] port: Fix first activity from bot to user has fake replyToId value (#2145) * Fix first activity from bot to user has fake replyToId value * Removed duplicate activity_id --- .../botbuilder/core/adapters/test_adapter.py | 4 +++- .../botbuilder/schema/_models_py3.py | 21 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 79070d732..77f566625 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -142,7 +142,9 @@ async def process_activity( if activity.type is None: activity.type = ActivityTypes.message - activity.channel_id = self.template.channel_id + if activity.channel_id is None: + activity.channel_id = self.template.channel_id + activity.from_property = self.template.from_property activity.recipient = self.template.recipient activity.conversation = self.template.conversation diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index e9eb1529d..b75cc9f82 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -639,7 +639,12 @@ def create_reply(self, text: str = None, locale: str = None): id=self.from_property.id if self.from_property else None, name=self.from_property.name if self.from_property else None, ), - reply_to_id=self.id, + reply_to_id=( + self.id + if not type == ActivityTypes.conversation_update + or self.channel_id not in ["directline", "webchat"] + else None + ), service_url=self.service_url, channel_id=self.channel_id, conversation=ConversationAccount( @@ -681,7 +686,12 @@ def create_trace( id=self.from_property.id if self.from_property else None, name=self.from_property.name if self.from_property else None, ), - reply_to_id=self.id, + reply_to_id=( + self.id + if not type == ActivityTypes.conversation_update + or self.channel_id not in ["directline", "webchat"] + else None + ), service_url=self.service_url, channel_id=self.channel_id, conversation=ConversationAccount( @@ -737,7 +747,12 @@ def get_conversation_reference(self): :returns: A conversation reference for the conversation that contains this activity. """ return ConversationReference( - activity_id=self.id, + activity_id=( + self.id + if not type == ActivityTypes.conversation_update + or self.channel_id not in ["directline", "webchat"] + else None + ), user=self.from_property, bot=self.recipient, conversation=self.conversation, From 51e3cdabbcdceffd40fdfb2512b315d85d810b7c Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:27:20 +0530 Subject: [PATCH 068/102] Bump PyYAML 6.0 to 6.0.1 (#2151) --- libraries/botframework-connector/tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botframework-connector/tests/requirements.txt b/libraries/botframework-connector/tests/requirements.txt index dfbb418bd..d6c057b7e 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~=7.3.1 -pyyaml==6.0 +pyyaml==6.0.1 pytest-asyncio==0.15.1 ddt==1.2.1 \ No newline at end of file From 734b6d1e98b085e3ccbcecc95efc235df5680bc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:52:55 -0500 Subject: [PATCH 069/102] Bump the pip group across 2 directories with 1 update (#2152) Bumps the pip group with 1 update in the /libraries/botbuilder-adapters-slack directory: [aiohttp](https://github.com/aio-libs/aiohttp). Bumps the pip group with 1 update in the /libraries/botbuilder-integration-aiohttp directory: [aiohttp](https://github.com/aio-libs/aiohttp). Updates `aiohttp` from 3.9.5 to 3.10.2 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.2) Updates `aiohttp` from 3.9.5 to 3.10.2 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.2) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 8d2c7b043..88ce85a22 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.9.5 +aiohttp==3.10.2 pyslack botbuilder-core==4.17.0 slackclient diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 9ce978580..6228ea580 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.17.0 botbuilder-schema==4.17.0 -aiohttp==3.9.5 +aiohttp==3.10.2 From 86673e2054ac80b7ed1f0939a45739aa3afe17a5 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 30 Aug 2024 12:34:54 -0500 Subject: [PATCH 070/102] Update botbuilder-python-ci.yml for Azure Pipelines --- pipelines/botbuilder-python-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/botbuilder-python-ci.yml b/pipelines/botbuilder-python-ci.yml index 6460bb907..b622bab3f 100644 --- a/pipelines/botbuilder-python-ci.yml +++ b/pipelines/botbuilder-python-ci.yml @@ -59,7 +59,7 @@ jobs: pip install -r ./libraries/botbuilder-core/tests/requirements.txt pip install -r ./libraries/botbuilder-ai/tests/requirements.txt pip install coveralls - pip install pylint==2.17 + pip install pylint==3.2.6 pip install black==24.4.2 displayName: 'Install dependencies' From b80253582cfa57faef55d8579cc953fbb6f4beee Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:39:58 +0530 Subject: [PATCH 071/102] aiohttp 3.10.2 (#2158) * aiohttp 3.10.2 * Fixing pylint errors --------- Co-authored-by: tracyboehrer --- libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py | 11 ++++++++++- libraries/botbuilder-ai/setup.py | 2 +- .../flask/flask_telemetry_middleware.py | 2 ++ .../botbuilder/dialogs/choices/choice_factory.py | 3 +-- .../botbuilder-dialogs/botbuilder/dialogs/dialog.py | 3 +-- .../aiohttp/streaming/aiohttp_web_socket.py | 2 ++ libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../setup.py | 2 +- .../botbuilder/schema/_models_py3.py | 6 +++--- 9 files changed, 22 insertions(+), 11 deletions(-) diff --git a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py index 825e08e8e..773c487e6 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py +++ b/libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import asyncio import json from typing import Dict, List, NamedTuple, Union from aiohttp import ClientSession, ClientTimeout @@ -52,8 +53,16 @@ def __init__( opt = options or QnAMakerOptions() self._validate_options(opt) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + instance_timeout = ClientTimeout(total=opt.timeout / 1000) - self._http_client = http_client or ClientSession(timeout=instance_timeout) + self._http_client = http_client or ClientSession( + timeout=instance_timeout, loop=loop + ) self.telemetry_client: Union[BotTelemetryClient, NullTelemetryClient] = ( telemetry_client or NullTelemetryClient() diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 105f1a4c9..2242efed9 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.17.0", "botbuilder-core==4.17.0", - "aiohttp==3.9.5", + "aiohttp==3.10.2", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/flask/flask_telemetry_middleware.py b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/flask/flask_telemetry_middleware.py index 8003074c9..5cc2676f2 100644 --- a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/flask/flask_telemetry_middleware.py +++ b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/flask/flask_telemetry_middleware.py @@ -38,6 +38,8 @@ def __call__(self, environ, start_response): def process_request(self, environ) -> bool: """Process the incoming Flask request.""" + body_unicode = None + # Bot Service doesn't handle anything over 256k length = int(environ.get("CONTENT_LENGTH", "0")) if length > 256 * 1024: diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py index 0e5edd8e1..ef1dfc117 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py @@ -46,8 +46,7 @@ def for_channel( else: size = len(choice.value) - if size > max_title_length: - max_title_length = size + max_title_length = max(max_title_length, size) # Determine list style supports_suggested_actions = Channel.supports_suggested_actions( diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py index 22dfe342b..43cfe3052 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py @@ -176,7 +176,6 @@ def _register_source_location( Registers a SourceRange in the provided location. :param path: The path to the source file. :param line_number: The line number where the source will be located on the file. - :return: """ if path: # This will be added when debbuging support is ported. @@ -185,4 +184,4 @@ def _register_source_location( # start_point = SourcePoint(line_index = line_number, char_index = 0 ), # end_point = SourcePoint(line_index = line_number + 1, char_index = 0 ), # ) - return + pass diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py index 2cd4ec13b..aa4a94a8e 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/streaming/aiohttp_web_socket.py @@ -40,6 +40,8 @@ async def receive(self) -> WebSocketMessage: try: message = await self._aiohttp_ws.receive() + message_data = None + if message.type == WSMsgType.TEXT: message_data = list(str(message.data).encode("ascii")) elif message.type == WSMsgType.BINARY: diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index 891647bb7..a777d50c9 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.17.0", "botbuilder-core==4.17.0", "yarl>=1.8.1", - "aiohttp==3.9.5", + "aiohttp==3.10.2", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index d40487403..d8b0f09cf 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.9.5", + "aiohttp==3.10.2", "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", "botbuilder-core==4.17.0", diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index b75cc9f82..9976c9809 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -641,7 +641,7 @@ def create_reply(self, text: str = None, locale: str = None): ), reply_to_id=( self.id - if not type == ActivityTypes.conversation_update + if type != ActivityTypes.conversation_update or self.channel_id not in ["directline", "webchat"] else None ), @@ -688,7 +688,7 @@ def create_trace( ), reply_to_id=( self.id - if not type == ActivityTypes.conversation_update + if type != ActivityTypes.conversation_update or self.channel_id not in ["directline", "webchat"] else None ), @@ -749,7 +749,7 @@ def get_conversation_reference(self): return ConversationReference( activity_id=( self.id - if not type == ActivityTypes.conversation_update + if type != ActivityTypes.conversation_update or self.channel_id not in ["directline", "webchat"] else None ), From f3d50f8c6c13b9015fdc126f922cdcdccb9998ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:20:49 -0500 Subject: [PATCH 072/102] Bump aiohttp (#2157) Bumps the pip group with 1 update in the /libraries/botbuilder-integration-aiohttp directory: [aiohttp](https://github.com/aio-libs/aiohttp). Updates `aiohttp` from 3.10.2 to 3.10.5 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.5) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 6228ea580..de0f13750 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.17.0 botbuilder-schema==4.17.0 -aiohttp==3.10.2 +aiohttp==3.10.5 From bb1561e8a1648e8d1d50c055c6fbaf7181ae7195 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:03:50 +0530 Subject: [PATCH 073/102] Fixing python313 deprecation warnings (#2159) * Fixing DeprecationWarnings in Python 3.13.0rc1 * Fix black issues * Fixing pylint issue --- .../botbuilder-ai/botbuilder/ai/luis/activity_util.py | 4 ++-- libraries/botbuilder-ai/tests/qna/test_qna.py | 4 +--- .../processor/telemetry_processor.py | 2 +- libraries/botbuilder-applicationinsights/setup.py | 2 +- .../botbuilder/core/adapters/test_adapter.py | 4 ++-- .../botbuilder/core/inspection/trace_activity.py | 8 ++++---- .../botbuilder/core/transcript_logger.py | 6 +++--- .../botbuilder-core/botbuilder/core/turn_context.py | 4 ++-- .../botbuilder/dialogs/prompts/oauth_prompt.py | 6 +++++- .../botbuilder-schema/botbuilder/schema/_models_py3.py | 6 +++--- libraries/botframework-connector/azure_bdist_wheel.py | 4 +--- .../botframework-connector/tests/requirements.txt | 5 +++-- .../botframework-connector/tests/test_attachments.py | 10 +++++++++- 13 files changed, 37 insertions(+), 28 deletions(-) diff --git a/libraries/botbuilder-ai/botbuilder/ai/luis/activity_util.py b/libraries/botbuilder-ai/botbuilder/ai/luis/activity_util.py index d2656a3ba..303917fbb 100644 --- a/libraries/botbuilder-ai/botbuilder/ai/luis/activity_util.py +++ b/libraries/botbuilder-ai/botbuilder/ai/luis/activity_util.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from datetime import datetime +from datetime import datetime, timezone from botbuilder.schema import ( Activity, @@ -51,7 +51,7 @@ def create_trace( reply = Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), from_property=from_property, recipient=ChannelAccount( id=turn_activity.from_property.id, name=turn_activity.from_property.name diff --git a/libraries/botbuilder-ai/tests/qna/test_qna.py b/libraries/botbuilder-ai/tests/qna/test_qna.py index 236594ac0..8a3f595ed 100644 --- a/libraries/botbuilder-ai/tests/qna/test_qna.py +++ b/libraries/botbuilder-ai/tests/qna/test_qna.py @@ -347,8 +347,6 @@ async def test_trace_test(self): self._knowledge_base_id, trace_activity.value.knowledge_base_id ) - return result - async def test_returns_answer_with_timeout(self): question: str = "how do I clean the stove?" options = QnAMakerOptions(timeout=999999) @@ -823,7 +821,7 @@ async def test_call_train(self): QnAMaker, "call_train", return_value=None ) as mocked_call_train: qna = QnAMaker(QnaApplicationTest.tests_endpoint) - qna.call_train(feedback_records) + await qna.call_train(feedback_records) mocked_call_train.assert_called_once_with(feedback_records) diff --git a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/processor/telemetry_processor.py b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/processor/telemetry_processor.py index dfe451e3f..0802f3cdf 100644 --- a/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/processor/telemetry_processor.py +++ b/libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/processor/telemetry_processor.py @@ -3,7 +3,7 @@ import base64 import json from abc import ABC, abstractmethod -from _sha256 import sha256 +from hashlib import sha256 class TelemetryProcessor(ABC): diff --git a/libraries/botbuilder-applicationinsights/setup.py b/libraries/botbuilder-applicationinsights/setup.py index 0932ff98f..9573e27f2 100644 --- a/libraries/botbuilder-applicationinsights/setup.py +++ b/libraries/botbuilder-applicationinsights/setup.py @@ -12,7 +12,7 @@ ] TESTS_REQUIRES = [ "aiounittest==1.3.0", - "django==3.2.24", # For samples + "django==4.2.15", # For samples "djangorestframework==3.14.0", # For samples "flask==2.2.5", # For samples ] diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 77f566625..ebfeb303a 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -8,7 +8,7 @@ import asyncio import inspect import uuid -from datetime import datetime +from datetime import datetime, timezone from uuid import uuid4 from typing import Awaitable, Coroutine, Dict, List, Callable, Union from copy import copy @@ -155,7 +155,7 @@ async def process_activity( finally: self._conversation_lock.release() - activity.timestamp = activity.timestamp or datetime.utcnow() + activity.timestamp = activity.timestamp or datetime.now(timezone.utc) await self.run_pipeline(self.create_turn_context(activity), logic) async def send_activities( diff --git a/libraries/botbuilder-core/botbuilder/core/inspection/trace_activity.py b/libraries/botbuilder-core/botbuilder/core/inspection/trace_activity.py index 307ef64cd..37cb33151 100644 --- a/libraries/botbuilder-core/botbuilder/core/inspection/trace_activity.py +++ b/libraries/botbuilder-core/botbuilder/core/inspection/trace_activity.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, Union from botbuilder.core import BotState @@ -11,7 +11,7 @@ def make_command_activity(command: str) -> Activity: return Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), name="Command", label="Command", value=command, @@ -22,7 +22,7 @@ def make_command_activity(command: str) -> Activity: def from_activity(activity: Activity, name: str, label: str) -> Activity: return Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), name=name, label=label, value=activity, @@ -33,7 +33,7 @@ def from_activity(activity: Activity, name: str, label: str) -> Activity: def from_state(bot_state: Union[BotState, Dict]) -> Activity: return Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), name="Bot State", label="BotState", value=bot_state, diff --git a/libraries/botbuilder-core/botbuilder/core/transcript_logger.py b/libraries/botbuilder-core/botbuilder/core/transcript_logger.py index e9536c1b6..5aa1ea726 100644 --- a/libraries/botbuilder-core/botbuilder/core/transcript_logger.py +++ b/libraries/botbuilder-core/botbuilder/core/transcript_logger.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. """Logs incoming and outgoing activities to a TranscriptStore..""" -import datetime +from datetime import datetime, timezone import copy import random import string @@ -86,11 +86,11 @@ async def send_activities_handler( prefix = "g_" + "".join( random.choice(alphanumeric) for i in range(5) ) - epoch = datetime.datetime.utcfromtimestamp(0) + epoch = datetime.fromtimestamp(0, timezone.utc) if cloned_activity.timestamp: reference = cloned_activity.timestamp else: - reference = datetime.datetime.today() + reference = datetime.now(timezone.utc) delta = (reference - epoch).total_seconds() * 1000 cloned_activity.id = f"{prefix}{delta}" await self.log_activity(transcript, cloned_activity) diff --git a/libraries/botbuilder-core/botbuilder/core/turn_context.py b/libraries/botbuilder-core/botbuilder/core/turn_context.py index 90ab99bd0..6c4a4eef3 100644 --- a/libraries/botbuilder-core/botbuilder/core/turn_context.py +++ b/libraries/botbuilder-core/botbuilder/core/turn_context.py @@ -3,7 +3,7 @@ import re from copy import copy, deepcopy -from datetime import datetime +from datetime import datetime, timezone from typing import List, Callable, Union, Dict from botframework.connector import Channels from botbuilder.schema import ( @@ -308,7 +308,7 @@ async def send_trace_activity( ) -> ResourceResponse: trace_activity = Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), name=name, value=value, value_type=value_type, diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py index c5b066913..d31a0b56a 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py @@ -420,7 +420,11 @@ async def _recognize_token( ) elif OAuthPrompt._is_teams_verification_invoke(context): - code = context.activity.value["state"] + code = ( + context.activity.value.get("state", None) + if context.activity.value + else None + ) try: token = await _UserTokenAccess.get_user_token( context, self._settings, code diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 9976c9809..1b6a631c6 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -4,7 +4,7 @@ from typing import List from botbuilder.schema._connector_client_enums import ActivityTypes -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from msrest.serialization import Model from msrest.exceptions import HttpOperationError @@ -630,7 +630,7 @@ def create_reply(self, text: str = None, locale: str = None): """ return Activity( type=ActivityTypes.message, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), from_property=ChannelAccount( id=self.recipient.id if self.recipient else None, name=self.recipient.name if self.recipient else None, @@ -677,7 +677,7 @@ def create_trace( return Activity( type=ActivityTypes.trace, - timestamp=datetime.utcnow(), + timestamp=datetime.now(timezone.utc), from_property=ChannelAccount( id=self.recipient.id if self.recipient else None, name=self.recipient.name if self.recipient else None, diff --git a/libraries/botframework-connector/azure_bdist_wheel.py b/libraries/botframework-connector/azure_bdist_wheel.py index d33af36bd..56a1b0b20 100644 --- a/libraries/botframework-connector/azure_bdist_wheel.py +++ b/libraries/botframework-connector/azure_bdist_wheel.py @@ -555,9 +555,7 @@ def write_record(self, bdist_dir, distinfo_dir): for azure_sub_package in folder_with_init: init_file = os.path.join(bdist_dir, azure_sub_package, "__init__.py") if os.path.isfile(init_file): - logger.info( - "manually remove {} while building the wheel".format(init_file) - ) + logger.info("manually remove %s while building the wheel", init_file) os.remove(init_file) else: raise ValueError( diff --git a/libraries/botframework-connector/tests/requirements.txt b/libraries/botframework-connector/tests/requirements.txt index d6c057b7e..5f0d9558d 100644 --- a/libraries/botframework-connector/tests/requirements.txt +++ b/libraries/botframework-connector/tests/requirements.txt @@ -1,5 +1,6 @@ pytest-cov>=2.6.0 pytest~=7.3.1 pyyaml==6.0.1 -pytest-asyncio==0.15.1 -ddt==1.2.1 \ No newline at end of file +pytest-asyncio==0.23.8 +ddt==1.2.1 +setuptools==72.1.0 \ No newline at end of file diff --git a/libraries/botframework-connector/tests/test_attachments.py b/libraries/botframework-connector/tests/test_attachments.py index b6d171250..a4b8b36b8 100644 --- a/libraries/botframework-connector/tests/test_attachments.py +++ b/libraries/botframework-connector/tests/test_attachments.py @@ -46,7 +46,15 @@ def read_base64(path_to_file): return encoded_string -LOOP = asyncio.get_event_loop() +# Ensure there's an event loop and get the auth token +# LOOP = asyncio.get_event_loop() +try: + LOOP = asyncio.get_running_loop() +except RuntimeError: + LOOP = asyncio.new_event_loop() + asyncio.set_event_loop(LOOP) + +# Run the async function to get the auth token AUTH_TOKEN = LOOP.run_until_complete(get_auth_token()) From 693acf0f18330a236d9293e63731df1943f249dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:37:51 -0500 Subject: [PATCH 074/102] Bump the pip group across 3 directories with 1 update (#2163) Bumps the pip group with 1 update in the /libraries/botbuilder-core directory: [cryptography](https://github.com/pyca/cryptography). Bumps the pip group with 1 update in the /libraries/botbuilder-dialogs directory: [cryptography](https://github.com/pyca/cryptography). Bumps the pip group with 1 update in the /libraries/botframework-connector directory: [cryptography](https://github.com/pyca/cryptography). Updates `cryptography` from 42.0.4 to 43.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...43.0.1) Updates `cryptography` from 42.0.4 to 43.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...43.0.1) Updates `cryptography` from 42.0.4 to 43.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...43.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production dependency-group: pip - dependency-name: cryptography dependency-type: direct:production dependency-group: pip - dependency-name: cryptography dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/botbuilder-core/requirements.txt | 2 +- libraries/botbuilder-dialogs/requirements.txt | 2 +- libraries/botframework-connector/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-core/requirements.txt b/libraries/botbuilder-core/requirements.txt index 6ce30f68f..4b9aabc5a 100644 --- a/libraries/botbuilder-core/requirements.txt +++ b/libraries/botbuilder-core/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.17.0 botframework-streaming==4.17.0 requests==2.32.0 PyJWT==2.4.0 -cryptography==42.0.4 +cryptography==43.0.1 aiounittest==1.3.0 \ No newline at end of file diff --git a/libraries/botbuilder-dialogs/requirements.txt b/libraries/botbuilder-dialogs/requirements.txt index 920200124..d8f2cb4f2 100644 --- a/libraries/botbuilder-dialogs/requirements.txt +++ b/libraries/botbuilder-dialogs/requirements.txt @@ -4,5 +4,5 @@ botbuilder-schema==4.17.0 botbuilder-core==4.17.0 requests==2.32.0 PyJWT==2.4.0 -cryptography==42.0.4 +cryptography==43.0.1 aiounittest==1.3.0 diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 0632606a7..5a6d8d4e9 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -2,5 +2,5 @@ msrest==0.7.* botbuilder-schema==4.17.0 requests==2.32.0 PyJWT==2.4.0 -cryptography==42.0.4 +cryptography==43.0.1 msal>=1.29.0 From 31dd5693bc10e79800d352c21869ae533ebcb74a Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:00:44 +0530 Subject: [PATCH 075/102] Bump aiohttp (#2164) --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-ai/setup.py | 2 +- libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../botbuilder-integration-applicationinsights-aiohttp/setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 88ce85a22..98af56627 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.10.2 +aiohttp==3.10.5 pyslack botbuilder-core==4.17.0 slackclient diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 2242efed9..e842fdc34 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.17.0", "botbuilder-core==4.17.0", - "aiohttp==3.10.2", + "aiohttp==3.10.5", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index a777d50c9..1fe5d5ccc 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.17.0", "botbuilder-core==4.17.0", "yarl>=1.8.1", - "aiohttp==3.10.2", + "aiohttp==3.10.5", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index d8b0f09cf..30c45b2f7 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.10.2", + "aiohttp==3.10.5", "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", "botbuilder-core==4.17.0", From 1e5102b2e4b7dd6b8fd75d1553b4f282557e7e6e Mon Sep 17 00:00:00 2001 From: Nikita-Gz <61112041+Nikita-Gz@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:38:03 +0300 Subject: [PATCH 076/102] Fixed type hint in turn_context.py (#2147) Previous type hint implied that send_activity will always return ResourceResponse, meanwhile it can also return None Co-authored-by: tracyboehrer --- libraries/botbuilder-core/botbuilder/core/turn_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/turn_context.py b/libraries/botbuilder-core/botbuilder/core/turn_context.py index 6c4a4eef3..852fd1f31 100644 --- a/libraries/botbuilder-core/botbuilder/core/turn_context.py +++ b/libraries/botbuilder-core/botbuilder/core/turn_context.py @@ -158,7 +158,7 @@ async def send_activity( activity_or_text: Union[Activity, str], speak: str = None, input_hint: str = None, - ) -> ResourceResponse: + ) -> Union[ResourceResponse, None]: """ Sends a single activity or message to the user. :param activity_or_text: From e493609b831f15c6f3dd80d939d9ad88b4ca2922 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 10 Sep 2024 20:38:23 +0100 Subject: [PATCH 077/102] Remove urllib3 version pin (#2144) Co-authored-by: tracyboehrer --- libraries/botbuilder-schema/setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-schema/setup.py b/libraries/botbuilder-schema/setup.py index 2075a5f20..43855c655 100644 --- a/libraries/botbuilder-schema/setup.py +++ b/libraries/botbuilder-schema/setup.py @@ -6,7 +6,10 @@ NAME = "botbuilder-schema" VERSION = os.environ["packageVersion"] if "packageVersion" in os.environ else "4.17.0" -REQUIRES = ["msrest== 0.7.*", "urllib3<2.0.0"] +REQUIRES = [ + "msrest== 0.7.*", + "urllib3", +] root = os.path.abspath(os.path.dirname(__file__)) From b3e74361f3802a003e9fb1bf59a5ad8054a7f67f Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 10 Sep 2024 15:11:09 -0500 Subject: [PATCH 078/102] Fixed ExpectReplies response (#2166) * Fixed ExpectReplies response * Fix formatting --------- Co-authored-by: Tracy Boehrer --- .../botbuilder-core/botbuilder/core/cloud_adapter_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py index 454861257..0f695a2a7 100644 --- a/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py +++ b/libraries/botbuilder-core/botbuilder/core/cloud_adapter_base.py @@ -436,7 +436,9 @@ def _process_turn_results(self, context: TurnContext) -> InvokeResponse: if context.activity.delivery_mode == DeliveryModes.expect_replies: return InvokeResponse( status=HTTPStatus.OK, - body=ExpectedReplies(activities=context.buffered_reply_activities), + body=ExpectedReplies( + activities=context.buffered_reply_activities + ).serialize(), ) # Handle Invoke scenarios where the bot will return a specific body and return code. From 50e72c0fa8a1991bb474e871987d8fff2b1bfc6e Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 10 Sep 2024 15:18:57 -0500 Subject: [PATCH 079/102] Updated azure-cosmos to 4.7.0, requiring dropped support for obsolete CosmosDBStorage class. (#2165) Co-authored-by: Tracy Boehrer --- .../botbuilder/azure/__init__.py | 4 +- .../azure/cosmosdb_partitioned_storage.py | 179 +++++---- .../botbuilder/azure/cosmosdb_storage.py | 378 ------------------ libraries/botbuilder-azure/setup.py | 2 +- .../tests/test_cosmos_partitioned_storage.py | 13 +- .../tests/test_cosmos_storage.py | 300 -------------- .../botbuilder/testing/storage_base_tests.py | 4 +- 7 files changed, 105 insertions(+), 775 deletions(-) delete mode 100644 libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py delete mode 100644 libraries/botbuilder-azure/tests/test_cosmos_storage.py diff --git a/libraries/botbuilder-azure/botbuilder/azure/__init__.py b/libraries/botbuilder-azure/botbuilder/azure/__init__.py index e625500a3..e6c70e7fc 100644 --- a/libraries/botbuilder-azure/botbuilder/azure/__init__.py +++ b/libraries/botbuilder-azure/botbuilder/azure/__init__.py @@ -7,10 +7,10 @@ from .about import __version__ from .azure_queue_storage import AzureQueueStorage -from .cosmosdb_storage import CosmosDbStorage, CosmosDbConfig, CosmosDbKeyEscape from .cosmosdb_partitioned_storage import ( CosmosDbPartitionedStorage, CosmosDbPartitionedConfig, + CosmosDbKeyEscape, ) from .blob_storage import BlobStorage, BlobStorageSettings @@ -18,8 +18,6 @@ "AzureQueueStorage", "BlobStorage", "BlobStorageSettings", - "CosmosDbStorage", - "CosmosDbConfig", "CosmosDbKeyEscape", "CosmosDbPartitionedStorage", "CosmosDbPartitionedConfig", diff --git a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_partitioned_storage.py b/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_partitioned_storage.py index 982ac5974..cfe66f8d8 100644 --- a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_partitioned_storage.py +++ b/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_partitioned_storage.py @@ -6,14 +6,14 @@ from typing import Dict, List from threading import Lock import json - +from hashlib import sha256 +from azure.core import MatchConditions from azure.cosmos import documents, http_constants from jsonpickle.pickler import Pickler from jsonpickle.unpickler import Unpickler import azure.cosmos.cosmos_client as cosmos_client # pylint: disable=no-name-in-module,import-error -import azure.cosmos.errors as cosmos_errors # pylint: disable=no-name-in-module,import-error +import azure.cosmos.exceptions as cosmos_exceptions from botbuilder.core.storage import Storage -from botbuilder.azure import CosmosDbKeyEscape class CosmosDbPartitionedConfig: @@ -63,6 +63,49 @@ def __init__( self.compatibility_mode = compatibility_mode or kwargs.get("compatibility_mode") +class CosmosDbKeyEscape: + @staticmethod + def sanitize_key( + key: str, key_suffix: str = "", compatibility_mode: bool = True + ) -> str: + """Return the sanitized key. + + Replace characters that are not allowed in keys in Cosmos. + + :param key: The provided key to be escaped. + :param key_suffix: The string to add a the end of all RowKeys. + :param compatibility_mode: True if keys should be truncated in order to support previous CosmosDb + max key length of 255. This behavior can be overridden by setting + cosmosdb_partitioned_config.compatibility_mode to False. + :return str: + """ + # forbidden characters + bad_chars = ["\\", "?", "/", "#", "\t", "\n", "\r", "*"] + # replace those with with '*' and the + # Unicode code point of the character and return the new string + key = "".join(map(lambda x: "*" + str(ord(x)) if x in bad_chars else x, key)) + + if key_suffix is None: + key_suffix = "" + + return CosmosDbKeyEscape.truncate_key(f"{key}{key_suffix}", compatibility_mode) + + @staticmethod + def truncate_key(key: str, compatibility_mode: bool = True) -> str: + max_key_len = 255 + + if not compatibility_mode: + return key + + if len(key) > max_key_len: + aux_hash = sha256(key.encode("utf-8")) + aux_hex = aux_hash.hexdigest() + + key = key[0 : max_key_len - len(aux_hex)] + aux_hex + + return key + + class CosmosDbPartitionedStorage(Storage): """A CosmosDB based storage provider using partitioning for a bot.""" @@ -99,7 +142,8 @@ async def read(self, keys: List[str]) -> Dict[str, object]: :return dict: """ if not keys: - raise Exception("Keys are required when reading") + # No keys passed in, no result to return. Back-compat with original CosmosDBStorage. + return {} await self.initialize() @@ -111,8 +155,8 @@ async def read(self, keys: List[str]) -> Dict[str, object]: key, self.config.key_suffix, self.config.compatibility_mode ) - read_item_response = self.client.ReadItem( - self.__item_link(escaped_key), self.__get_partition_key(escaped_key) + read_item_response = self.container.read_item( + escaped_key, self.__get_partition_key(escaped_key) ) document_store_item = read_item_response if document_store_item: @@ -122,13 +166,8 @@ async def read(self, keys: List[str]) -> Dict[str, object]: # When an item is not found a CosmosException is thrown, but we want to # return an empty collection so in this instance we catch and do not rethrow. # Throw for any other exception. - except cosmos_errors.HTTPFailure as err: - if ( - err.status_code - == cosmos_errors.http_constants.StatusCodes.NOT_FOUND - ): - continue - raise err + except cosmos_exceptions.CosmosResourceNotFoundError: + continue except Exception as err: raise err return store_items @@ -162,20 +201,16 @@ async def write(self, changes: Dict[str, object]): if e_tag == "": raise Exception("cosmosdb_storage.write(): etag missing") - access_condition = { - "accessCondition": {"type": "IfMatch", "condition": e_tag} - } - options = ( - access_condition if e_tag != "*" and e_tag and e_tag != "" else None - ) + access_condition = e_tag != "*" and e_tag and e_tag != "" + try: - self.client.UpsertItem( - database_or_Container_link=self.__container_link, - document=doc, - options=options, + self.container.upsert_item( + body=doc, + etag=e_tag if access_condition else None, + match_condition=( + MatchConditions.IfNotModified if access_condition else None + ), ) - except cosmos_errors.HTTPFailure as err: - raise err except Exception as err: raise err @@ -192,69 +227,66 @@ async def delete(self, keys: List[str]): key, self.config.key_suffix, self.config.compatibility_mode ) try: - self.client.DeleteItem( - document_link=self.__item_link(escaped_key), - options=self.__get_partition_key(escaped_key), + self.container.delete_item( + escaped_key, + self.__get_partition_key(escaped_key), ) - except cosmos_errors.HTTPFailure as err: - if ( - err.status_code - == cosmos_errors.http_constants.StatusCodes.NOT_FOUND - ): - continue - raise err + except cosmos_exceptions.CosmosResourceNotFoundError: + continue except Exception as err: raise err async def initialize(self): if not self.container: if not self.client: + connection_policy = self.config.cosmos_client_options.get( + "connection_policy", documents.ConnectionPolicy() + ) + + # kwargs 'connection_verify' is to handle CosmosClient overwriting the + # ConnectionPolicy.DisableSSLVerification value. self.client = cosmos_client.CosmosClient( self.config.cosmos_db_endpoint, - {"masterKey": self.config.auth_key}, - self.config.cosmos_client_options.get("connection_policy", None), + self.config.auth_key, self.config.cosmos_client_options.get("consistency_level", None), + **{ + "connection_policy": connection_policy, + "connection_verify": not connection_policy.DisableSSLVerification, + }, ) if not self.database: with self.__lock: - try: - if not self.database: - self.database = self.client.CreateDatabase( - {"id": self.config.database_id} - ) - except cosmos_errors.HTTPFailure: - self.database = self.client.ReadDatabase( - "dbs/" + self.config.database_id + if not self.database: + self.database = self.client.create_database_if_not_exists( + self.config.database_id ) self.__get_or_create_container() def __get_or_create_container(self): with self.__lock: - container_def = { - "id": self.config.container_id, - "partitionKey": { - "paths": ["/id"], - "kind": documents.PartitionKind.Hash, - }, + partition_key = { + "paths": ["/id"], + "kind": documents.PartitionKind.Hash, } try: if not self.container: - self.container = self.client.CreateContainer( - "dbs/" + self.database["id"], - container_def, - {"offerThroughput": self.config.container_throughput}, + self.container = self.database.create_container( + self.config.container_id, + partition_key, + offer_throughput=self.config.container_throughput, ) - except cosmos_errors.HTTPFailure as err: + except cosmos_exceptions.CosmosHttpResponseError as err: if err.status_code == http_constants.StatusCodes.CONFLICT: - self.container = self.client.ReadContainer( - "dbs/" + self.database["id"] + "/colls/" + container_def["id"] + self.container = self.database.get_container_client( + self.config.container_id ) - if "partitionKey" not in self.container: + properties = self.container.read() + if "partitionKey" not in properties: self.compatability_mode_partition_key = True else: - paths = self.container["partitionKey"]["paths"] + paths = properties["partitionKey"]["paths"] if "/partitionKey" in paths: self.compatability_mode_partition_key = True elif "/id" not in paths: @@ -267,7 +299,7 @@ def __get_or_create_container(self): raise err def __get_partition_key(self, key: str) -> str: - return None if self.compatability_mode_partition_key else {"partitionKey": key} + return None if self.compatability_mode_partition_key else key @staticmethod def __create_si(result) -> object: @@ -303,28 +335,3 @@ def __create_dict(store_item: object) -> Dict: # loop through attributes and write and return a dict return json_dict - - def __item_link(self, identifier) -> str: - """Return the item link of a item in the container. - - :param identifier: - :return str: - """ - return self.__container_link + "/docs/" + identifier - - @property - def __container_link(self) -> str: - """Return the container link in the database. - - :param: - :return str: - """ - return self.__database_link + "/colls/" + self.config.container_id - - @property - def __database_link(self) -> str: - """Return the database link. - - :return str: - """ - return "dbs/" + self.config.database_id diff --git a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py b/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py deleted file mode 100644 index 2e383666f..000000000 --- a/libraries/botbuilder-azure/botbuilder/azure/cosmosdb_storage.py +++ /dev/null @@ -1,378 +0,0 @@ -"""Implements a CosmosDB based storage provider. -""" - -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -from hashlib import sha256 -from typing import Dict, List -from threading import Semaphore -import json -import warnings -from jsonpickle.pickler import Pickler -from jsonpickle.unpickler import Unpickler -import azure.cosmos.cosmos_client as cosmos_client # pylint: disable=no-name-in-module,import-error -import azure.cosmos.errors as cosmos_errors # pylint: disable=no-name-in-module,import-error -from botbuilder.core.storage import Storage - - -class CosmosDbConfig: - """The class for CosmosDB configuration for the Azure Bot Framework.""" - - def __init__( - self, - endpoint: str = None, - masterkey: str = None, - database: str = None, - container: str = None, - partition_key: str = None, - database_creation_options: dict = None, - container_creation_options: dict = None, - **kwargs, - ): - """Create the Config object. - - :param endpoint: - :param masterkey: - :param database: - :param container: - :param filename: - :return CosmosDbConfig: - """ - self.__config_file = kwargs.get("filename") - if self.__config_file: - kwargs = json.load(open(self.__config_file)) - self.endpoint = endpoint or kwargs.get("endpoint") - self.masterkey = masterkey or kwargs.get("masterkey") - self.database = database or kwargs.get("database", "bot_db") - self.container = container or kwargs.get("container", "bot_container") - self.partition_key = partition_key or kwargs.get("partition_key") - self.database_creation_options = database_creation_options or kwargs.get( - "database_creation_options" - ) - self.container_creation_options = container_creation_options or kwargs.get( - "container_creation_options" - ) - - -class CosmosDbKeyEscape: - @staticmethod - def sanitize_key( - key: str, key_suffix: str = "", compatibility_mode: bool = True - ) -> str: - """Return the sanitized key. - - Replace characters that are not allowed in keys in Cosmos. - - :param key: The provided key to be escaped. - :param key_suffix: The string to add a the end of all RowKeys. - :param compatibility_mode: True if keys should be truncated in order to support previous CosmosDb - max key length of 255. This behavior can be overridden by setting - cosmosdb_partitioned_config.compatibility_mode to False. - :return str: - """ - # forbidden characters - bad_chars = ["\\", "?", "/", "#", "\t", "\n", "\r", "*"] - # replace those with with '*' and the - # Unicode code point of the character and return the new string - key = "".join(map(lambda x: "*" + str(ord(x)) if x in bad_chars else x, key)) - - if key_suffix is None: - key_suffix = "" - - return CosmosDbKeyEscape.truncate_key(f"{key}{key_suffix}", compatibility_mode) - - @staticmethod - def truncate_key(key: str, compatibility_mode: bool = True) -> str: - max_key_len = 255 - - if not compatibility_mode: - return key - - if len(key) > max_key_len: - aux_hash = sha256(key.encode("utf-8")) - aux_hex = aux_hash.hexdigest() - - key = key[0 : max_key_len - len(aux_hex)] + aux_hex - - return key - - -class CosmosDbStorage(Storage): - """A CosmosDB based storage provider for a bot.""" - - def __init__( - self, config: CosmosDbConfig, client: cosmos_client.CosmosClient = None - ): - """Create the storage object. - - :param config: - """ - super(CosmosDbStorage, self).__init__() - warnings.warn( - "CosmosDbStorage is obsolete. Use CosmosDbPartitionedStorage instead." - ) - self.config = config - self.client = client or cosmos_client.CosmosClient( - self.config.endpoint, {"masterKey": self.config.masterkey} - ) - # these are set by the functions that check - # the presence of the database and container or creates them - self.database = None - self.container = None - self._database_creation_options = config.database_creation_options - self._container_creation_options = config.container_creation_options - self.__semaphore = Semaphore() - - async def read(self, keys: List[str]) -> Dict[str, object]: - """Read storeitems from storage. - - :param keys: - :return dict: - """ - try: - # check if the database and container exists and if not create - if not self.__container_exists: - self.__create_db_and_container() - if keys: - # create the parameters object - parameters = [ - { - "name": f"@id{i}", - "value": f"{CosmosDbKeyEscape.sanitize_key(key)}", - } - for i, key in enumerate(keys) - ] - # get the names of the params - parameter_sequence = ",".join(param.get("name") for param in parameters) - # create the query - query = { - "query": f"SELECT c.id, c.realId, c.document, c._etag FROM c WHERE c.id in ({parameter_sequence})", - "parameters": parameters, - } - - if self.config.partition_key: - options = {"partitionKey": self.config.partition_key} - else: - options = {"enableCrossPartitionQuery": True} - - # run the query and store the results as a list - results = list( - self.client.QueryItems(self.__container_link, query, options) - ) - # return a dict with a key and an object - return {r.get("realId"): self.__create_si(r) for r in results} - - # No keys passed in, no result to return. - return {} - except TypeError as error: - raise error - - async def write(self, changes: Dict[str, object]): - """Save storeitems to storage. - - :param changes: - :return: - """ - if changes is None: - raise Exception("Changes are required when writing") - if not changes: - return - try: - # check if the database and container exists and if not create - if not self.__container_exists: - self.__create_db_and_container() - # iterate over the changes - for key, change in changes.items(): - # store the e_tag - e_tag = None - if isinstance(change, dict): - e_tag = change.get("e_tag", None) - elif hasattr(change, "e_tag"): - e_tag = change.e_tag - # create the new document - doc = { - "id": CosmosDbKeyEscape.sanitize_key(key), - "realId": key, - "document": self.__create_dict(change), - } - if e_tag == "": - raise Exception("cosmosdb_storage.write(): etag missing") - # the e_tag will be * for new docs so do an insert - if e_tag == "*" or not e_tag: - self.client.UpsertItem( - database_or_Container_link=self.__container_link, - document=doc, - options={"disableAutomaticIdGeneration": True}, - ) - # if we have an etag, do opt. concurrency replace - elif e_tag: - access_condition = {"type": "IfMatch", "condition": e_tag} - self.client.ReplaceItem( - document_link=self.__item_link( - CosmosDbKeyEscape.sanitize_key(key) - ), - new_document=doc, - options={"accessCondition": access_condition}, - ) - except Exception as error: - raise error - - async def delete(self, keys: List[str]): - """Remove storeitems from storage. - - :param keys: - :return: - """ - try: - # check if the database and container exists and if not create - if not self.__container_exists: - self.__create_db_and_container() - - options = {} - if self.config.partition_key: - options["partitionKey"] = self.config.partition_key - - # call the function for each key - for key in keys: - self.client.DeleteItem( - document_link=self.__item_link(CosmosDbKeyEscape.sanitize_key(key)), - options=options, - ) - # print(res) - except cosmos_errors.HTTPFailure as http_failure: - # print(h.status_code) - if http_failure.status_code != 404: - raise http_failure - except TypeError as error: - raise error - - def __create_si(self, result) -> object: - """Create an object from a result out of CosmosDB. - - :param result: - :return object: - """ - # get the document item from the result and turn into a dict - doc = result.get("document") - # read the e_tag from Cosmos - if result.get("_etag"): - doc["e_tag"] = result["_etag"] - - result_obj = Unpickler().restore(doc) - - # create and return the object - return result_obj - - def __create_dict(self, store_item: object) -> Dict: - """Return the dict of an object. - - This eliminates non_magic attributes and the e_tag. - - :param store_item: - :return dict: - """ - # read the content - json_dict = Pickler().flatten(store_item) - if "e_tag" in json_dict: - del json_dict["e_tag"] - - # loop through attributes and write and return a dict - return json_dict - - def __item_link(self, identifier) -> str: - """Return the item link of a item in the container. - - :param identifier: - :return str: - """ - return self.__container_link + "/docs/" + identifier - - @property - def __container_link(self) -> str: - """Return the container link in the database. - - :param: - :return str: - """ - return self.__database_link + "/colls/" + self.container - - @property - def __database_link(self) -> str: - """Return the database link. - - :return str: - """ - return "dbs/" + self.database - - @property - def __container_exists(self) -> bool: - """Return whether the database and container have been created. - - :return bool: - """ - return self.database and self.container - - def __create_db_and_container(self): - """Call the get or create methods.""" - with self.__semaphore: - db_id = self.config.database - container_name = self.config.container - self.database = self._get_or_create_database(self.client, db_id) - self.container = self._get_or_create_container(self.client, container_name) - - def _get_or_create_database( # pylint: disable=invalid-name - self, doc_client, id - ) -> str: - """Return the database link. - - Check if the database exists or create the database. - - :param doc_client: - :param id: - :return str: - """ - # query CosmosDB for a database with that name/id - dbs = list( - doc_client.QueryDatabases( - { - "query": "SELECT * FROM r WHERE r.id=@id", - "parameters": [{"name": "@id", "value": id}], - } - ) - ) - # if there are results, return the first (database names are unique) - if dbs: - return dbs[0]["id"] - - # create the database if it didn't exist - res = doc_client.CreateDatabase({"id": id}, self._database_creation_options) - return res["id"] - - def _get_or_create_container(self, doc_client, container) -> str: - """Return the container link. - - Check if the container exists or create the container. - - :param doc_client: - :param container: - :return str: - """ - # query CosmosDB for a container in the database with that name - containers = list( - doc_client.QueryContainers( - self.__database_link, - { - "query": "SELECT * FROM r WHERE r.id=@id", - "parameters": [{"name": "@id", "value": container}], - }, - ) - ) - # if there are results, return the first (container names are unique) - if containers: - return containers[0]["id"] - - # Create a container if it didn't exist - res = doc_client.CreateContainer( - self.__database_link, {"id": container}, self._container_creation_options - ) - return res["id"] diff --git a/libraries/botbuilder-azure/setup.py b/libraries/botbuilder-azure/setup.py index 04fd479cb..9c40b3ab5 100644 --- a/libraries/botbuilder-azure/setup.py +++ b/libraries/botbuilder-azure/setup.py @@ -5,7 +5,7 @@ from setuptools import setup REQUIRES = [ - "azure-cosmos==3.2.0", + "azure-cosmos==4.7.0", "azure-storage-blob==12.7.0", "azure-storage-queue==12.4.0", "botbuilder-schema==4.17.0", diff --git a/libraries/botbuilder-azure/tests/test_cosmos_partitioned_storage.py b/libraries/botbuilder-azure/tests/test_cosmos_partitioned_storage.py index cb6dd0822..d52733fd9 100644 --- a/libraries/botbuilder-azure/tests/test_cosmos_partitioned_storage.py +++ b/libraries/botbuilder-azure/tests/test_cosmos_partitioned_storage.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import azure.cosmos.errors as cosmos_errors +import azure.cosmos.exceptions as cosmos_exceptions from azure.cosmos import documents import pytest from botbuilder.azure import CosmosDbPartitionedStorage, CosmosDbPartitionedConfig @@ -27,8 +27,8 @@ async def reset(): storage = CosmosDbPartitionedStorage(get_settings()) await storage.initialize() try: - storage.client.DeleteDatabase(database_link="dbs/" + get_settings().database_id) - except cosmos_errors.HTTPFailure: + storage.client.delete_database(get_settings().database_id) + except cosmos_exceptions.HttpResponseError: pass @@ -99,9 +99,12 @@ async def test_passes_cosmos_client_options(self): client = CosmosDbPartitionedStorage(settings_with_options) await client.initialize() - assert client.client.connection_policy.DisableSSLVerification is True assert ( - client.client.default_headers["x-ms-consistency-level"] + client.client.client_connection.connection_policy.DisableSSLVerification + is True + ) + assert ( + client.client.client_connection.default_headers["x-ms-consistency-level"] == documents.ConsistencyLevel.Eventual ) diff --git a/libraries/botbuilder-azure/tests/test_cosmos_storage.py b/libraries/botbuilder-azure/tests/test_cosmos_storage.py deleted file mode 100644 index c66660857..000000000 --- a/libraries/botbuilder-azure/tests/test_cosmos_storage.py +++ /dev/null @@ -1,300 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from unittest.mock import Mock -import azure.cosmos.errors as cosmos_errors -from azure.cosmos.cosmos_client import CosmosClient -import pytest -from botbuilder.core import StoreItem -from botbuilder.azure import CosmosDbStorage, CosmosDbConfig -from botbuilder.testing import StorageBaseTests - -# local cosmosdb emulator instance cosmos_db_config -COSMOS_DB_CONFIG = CosmosDbConfig( - endpoint="https://localhost:8081", - masterkey="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", - database="test-db", - container="bot-storage", -) -EMULATOR_RUNNING = False - - -def get_storage(): - return CosmosDbStorage(COSMOS_DB_CONFIG) - - -async def reset(): - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - try: - storage.client.DeleteDatabase(database_link="dbs/" + COSMOS_DB_CONFIG.database) - except cosmos_errors.HTTPFailure: - pass - - -def get_mock_client(identifier: str = "1"): - # pylint: disable=attribute-defined-outside-init, invalid-name - mock = MockClient() - - mock.QueryDatabases = Mock(return_value=[]) - mock.QueryContainers = Mock(return_value=[]) - mock.CreateDatabase = Mock(return_value={"id": identifier}) - mock.CreateContainer = Mock(return_value={"id": identifier}) - - return mock - - -class MockClient(CosmosClient): - def __init__(self): # pylint: disable=super-init-not-called - pass - - -class SimpleStoreItem(StoreItem): - def __init__(self, counter=1, e_tag="*"): - super(SimpleStoreItem, self).__init__() - self.counter = counter - self.e_tag = e_tag - - -class TestCosmosDbStorageConstructor: - @pytest.mark.asyncio - async def test_cosmos_storage_init_should_error_without_cosmos_db_config(self): - try: - CosmosDbStorage(CosmosDbConfig()) - except Exception as error: - assert error - - @pytest.mark.asyncio - async def test_creation_request_options_are_being_called(self): - # pylint: disable=protected-access - test_config = CosmosDbConfig( - endpoint="https://localhost:8081", - masterkey="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", - database="test-db", - container="bot-storage", - database_creation_options={"OfferThroughput": 1000}, - container_creation_options={"OfferThroughput": 500}, - ) - - test_id = "1" - client = get_mock_client(identifier=test_id) - storage = CosmosDbStorage(test_config, client) - storage.database = test_id - - assert storage._get_or_create_database(doc_client=client, id=test_id), test_id - client.CreateDatabase.assert_called_with( - {"id": test_id}, test_config.database_creation_options - ) - assert storage._get_or_create_container( - doc_client=client, container=test_id - ), test_id - client.CreateContainer.assert_called_with( - "dbs/" + test_id, {"id": test_id}, test_config.container_creation_options - ) - - -class TestCosmosDbStorageBaseStorageTests: - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_return_empty_object_when_reading_unknown_key(self): - await reset() - - test_ran = await StorageBaseTests.return_empty_object_when_reading_unknown_key( - get_storage() - ) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_handle_null_keys_when_reading(self): - await reset() - - test_ran = await StorageBaseTests.handle_null_keys_when_reading(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_handle_null_keys_when_writing(self): - await reset() - - test_ran = await StorageBaseTests.handle_null_keys_when_writing(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_does_not_raise_when_writing_no_items(self): - await reset() - - test_ran = await StorageBaseTests.does_not_raise_when_writing_no_items( - get_storage() - ) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_create_object(self): - await reset() - - test_ran = await StorageBaseTests.create_object(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_handle_crazy_keys(self): - await reset() - - test_ran = await StorageBaseTests.handle_crazy_keys(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_update_object(self): - await reset() - - test_ran = await StorageBaseTests.update_object(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_delete_object(self): - await reset() - - test_ran = await StorageBaseTests.delete_object(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_perform_batch_operations(self): - await reset() - - test_ran = await StorageBaseTests.perform_batch_operations(get_storage()) - - assert test_ran - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_proceeds_through_waterfall(self): - await reset() - - test_ran = await StorageBaseTests.proceeds_through_waterfall(get_storage()) - - assert test_ran - - -class TestCosmosDbStorage: - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_init_should_work_with_just_endpoint_and_key(self): - storage = CosmosDbStorage( - CosmosDbConfig( - endpoint=COSMOS_DB_CONFIG.endpoint, masterkey=COSMOS_DB_CONFIG.masterkey - ) - ) - await storage.write({"user": SimpleStoreItem()}) - data = await storage.read(["user"]) - assert "user" in data - assert data["user"].counter == 1 - assert len(data.keys()) == 1 - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_read_update_should_return_new_etag(self): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write({"test": SimpleStoreItem(counter=1)}) - data_result = await storage.read(["test"]) - data_result["test"].counter = 2 - await storage.write(data_result) - data_updated = await storage.read(["test"]) - assert data_updated["test"].counter == 2 - assert data_updated["test"].e_tag != data_result["test"].e_tag - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_read_with_invalid_key_should_return_empty_dict(self): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - data = await storage.read(["test"]) - - assert isinstance(data, dict) - assert not data.keys() - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_write_should_overwrite_when_new_e_tag_is_an_asterisk( - self, - ): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write({"user": SimpleStoreItem()}) - - await storage.write({"user": SimpleStoreItem(counter=10, e_tag="*")}) - data = await storage.read(["user"]) - assert data["user"].counter == 10 - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_delete_should_delete_multiple_values_when_given_multiple_valid_keys( - self, - ): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write({"test": SimpleStoreItem(), "test2": SimpleStoreItem(2)}) - - await storage.delete(["test", "test2"]) - data = await storage.read(["test", "test2"]) - assert not data.keys() - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_delete_should_delete_values_when_given_multiple_valid_keys_and_ignore_other_data( - self, - ): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write( - { - "test": SimpleStoreItem(), - "test2": SimpleStoreItem(counter=2), - "test3": SimpleStoreItem(counter=3), - } - ) - - await storage.delete(["test", "test2"]) - data = await storage.read(["test", "test2", "test3"]) - assert len(data.keys()) == 1 - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_delete_invalid_key_should_do_nothing_and_not_affect_cached_data( - self, - ): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write({"test": SimpleStoreItem()}) - - await storage.delete(["foo"]) - data = await storage.read(["test"]) - assert len(data.keys()) == 1 - data = await storage.read(["foo"]) - assert not data.keys() - - @pytest.mark.skipif(not EMULATOR_RUNNING, reason="Needs the emulator to run.") - @pytest.mark.asyncio - async def test_cosmos_storage_delete_invalid_keys_should_do_nothing_and_not_affect_cached_data( - self, - ): - await reset() - storage = CosmosDbStorage(COSMOS_DB_CONFIG) - await storage.write({"test": SimpleStoreItem()}) - - await storage.delete(["foo", "bar"]) - data = await storage.read(["test"]) - assert len(data.keys()) == 1 diff --git a/libraries/botbuilder-testing/botbuilder/testing/storage_base_tests.py b/libraries/botbuilder-testing/botbuilder/testing/storage_base_tests.py index e196099a0..e374a3401 100644 --- a/libraries/botbuilder-testing/botbuilder/testing/storage_base_tests.py +++ b/libraries/botbuilder-testing/botbuilder/testing/storage_base_tests.py @@ -24,7 +24,7 @@ async def test_handle_null_keys_when_reading(self): assert test_ran """ import pytest -from botbuilder.azure import CosmosDbStorage +from botbuilder.azure import CosmosDbPartitionedStorage from botbuilder.core import ( ConversationState, TurnContext, @@ -57,7 +57,7 @@ async def return_empty_object_when_reading_unknown_key(storage) -> bool: @staticmethod async def handle_null_keys_when_reading(storage) -> bool: - if isinstance(storage, (CosmosDbStorage, MemoryStorage)): + if isinstance(storage, (CosmosDbPartitionedStorage, MemoryStorage)): result = await storage.read(None) assert len(result.keys()) == 0 # Catch-all From 0fba27be15cd72bffb7f34bf1ed20c05c3df0957 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:37:15 +0530 Subject: [PATCH 080/102] port: Add Teams read receipt event (#6356) (#2167) * port: Add Teams read receipt event (#6356) * remove unused variables --- .../core/teams/teams_activity_handler.py | 18 +++++++ .../teams/test_teams_activity_handler.py | 27 ++++++++++ .../botbuilder/schema/teams/__init__.py | 2 + .../botbuilder/schema/teams/_models_py3.py | 51 +++++++++++++++++++ .../tests/teams/test_read_receipt_info.py | 28 ++++++++++ 5 files changed, 126 insertions(+) create mode 100644 libraries/botbuilder-schema/tests/teams/test_read_receipt_info.py diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 33b4e419c..4fd6d4ee1 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -28,6 +28,7 @@ TabRequest, TabSubmit, MeetingParticipantsEventDetails, + ReadReceiptInfo, ) from botframework.connector import Channels from ..serializer_helper import deserializer_helper @@ -906,6 +907,10 @@ async def on_event_activity(self, turn_context: TurnContext): the scope of a channel. """ if turn_context.activity.channel_id == Channels.ms_teams: + if turn_context.activity.name == "application/vnd.microsoft.readReceipt": + return await self.on_teams_read_receipt_event( + turn_context.activity.value, turn_context + ) if turn_context.activity.name == "application/vnd.microsoft.meetingStart": return await self.on_teams_meeting_start_event( turn_context.activity.value, turn_context @@ -931,6 +936,19 @@ async def on_event_activity(self, turn_context: TurnContext): return await super().on_event_activity(turn_context) + async def on_teams_read_receipt_event( + self, read_receipt_info: ReadReceiptInfo, turn_context: TurnContext + ): # pylint: disable=unused-argument + """ + Override this in a derived class to provide logic for when the bot receives a read receipt event. + + :param read_receipt_info: Information regarding the read receipt. i.e. Id of the message last read by the user. + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return + async def on_teams_meeting_start_event( self, meeting: MeetingStartEventDetails, turn_context: TurnContext ): # pylint: disable=unused-argument diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 390df6191..af9bcd4ab 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -33,6 +33,7 @@ TabSubmit, TabContext, MeetingParticipantsEventDetails, + ReadReceiptInfo, ) from botframework.connector import Channels from simple_adapter import SimpleAdapter @@ -318,6 +319,14 @@ async def on_event_activity(self, turn_context: TurnContext): self.record.append("on_event_activity") return await super().on_event_activity(turn_context) + async def on_teams_read_receipt_event( + self, read_receipt_info: ReadReceiptInfo, turn_context: TurnContext + ): + self.record.append("on_teams_read_receipt_event") + return await super().on_teams_read_receipt_event( + turn_context.activity.value, turn_context + ) + async def on_teams_meeting_start_event( self, meeting: MeetingStartEventDetails, turn_context: TurnContext ): @@ -1141,6 +1150,24 @@ async def test_typing_activity(self): assert len(bot.record) == 1 assert bot.record[0] == "on_typing_activity" + async def test_on_teams_read_receipt_event(self): + activity = Activity( + type=ActivityTypes.event, + name="application/vnd.microsoft.readReceipt", + channel_id=Channels.ms_teams, + value={"lastReadMessageId": "10101010"}, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + assert len(bot.record) == 2 + assert bot.record[0] == "on_event_activity" + assert bot.record[1] == "on_teams_read_receipt_event" + async def test_on_teams_meeting_start_event(self): activity = Activity( type=ActivityTypes.event, diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 55901f7a4..0bac60e96 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -80,6 +80,7 @@ from ._models_py3 import UserMeetingDetails from ._models_py3 import TeamsMeetingMember from ._models_py3 import MeetingParticipantsEventDetails +from ._models_py3 import ReadReceiptInfo __all__ = [ "AppBasedLinkQuery", @@ -161,4 +162,5 @@ "UserMeetingDetails", "TeamsMeetingMember", "MeetingParticipantsEventDetails", + "ReadReceiptInfo", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 260442cf8..a507467cc 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2568,3 +2568,54 @@ class MeetingParticipantsEventDetails(Model): def __init__(self, *, members: List[TeamsMeetingMember] = None, **kwargs) -> None: super(MeetingParticipantsEventDetails, self).__init__(**kwargs) self.members = members + + +class ReadReceiptInfo(Model): + """General information about a read receipt. + + :param last_read_message_id: The id of the last read message. + :type last_read_message_id: str + """ + + _attribute_map = { + "last_read_message_id": {"key": "lastReadMessageId", "type": "str"}, + } + + def __init__(self, *, last_read_message_id: str = None, **kwargs) -> None: + super(ReadReceiptInfo, self).__init__(**kwargs) + self.last_read_message_id = last_read_message_id + + @staticmethod + def is_message_read(compare_message_id, last_read_message_id): + """ + Helper method useful for determining if a message has been read. + This method converts the strings to integers. If the compare_message_id is + less than or equal to the last_read_message_id, then the message has been read. + + :param compare_message_id: The id of the message to compare. + :param last_read_message_id: The id of the last message read by the user. + :return: True if the compare_message_id is less than or equal to the last_read_message_id. + """ + if not compare_message_id or not last_read_message_id: + return False + + try: + compare_message_id_long = int(compare_message_id) + last_read_message_id_long = int(last_read_message_id) + except ValueError: + return False + + return compare_message_id_long <= last_read_message_id_long + + def is_message_read_instance(self, compare_message_id): + """ + Helper method useful for determining if a message has been read. + If the compare_message_id is less than or equal to the last_read_message_id, + then the message has been read. + + :param compare_message_id: The id of the message to compare. + :return: True if the compare_message_id is less than or equal to the last_read_message_id. + """ + return ReadReceiptInfo.is_message_read( + compare_message_id, self.last_read_message_id + ) diff --git a/libraries/botbuilder-schema/tests/teams/test_read_receipt_info.py b/libraries/botbuilder-schema/tests/teams/test_read_receipt_info.py new file mode 100644 index 000000000..e6aad9bf3 --- /dev/null +++ b/libraries/botbuilder-schema/tests/teams/test_read_receipt_info.py @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botbuilder.schema.teams import ReadReceiptInfo + + +class TestReadReceiptInfo(aiounittest.AsyncTestCase): + def test_read_receipt_info(self): + # Arrange + test_cases = [ + ("1000", "1000", True), + ("1001", "1000", True), + ("1000", "1001", False), + ("1000", None, False), + (None, "1000", False), + ] + + for last_read, compare, is_read in test_cases: + # Act + info = ReadReceiptInfo(last_read_message_id=last_read) + + # Assert + self.assertEqual(info.last_read_message_id, last_read) + self.assertEqual(info.is_message_read_instance(compare), is_read) + self.assertEqual( + ReadReceiptInfo.is_message_read(compare, last_read), is_read + ) From fddd368dca2c0889c01cf4ac101666236aa9298b Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:23:37 +0530 Subject: [PATCH 081/102] Add new Teams invoke types 'config/fetch' and 'config/submit' (#2170) --- .../core/teams/teams_activity_handler.py | 42 ++++++++++ .../teams/test_teams_activity_handler.py | 52 ++++++++++++ .../botbuilder/schema/teams/__init__.py | 8 ++ .../botbuilder/schema/teams/_models_py3.py | 80 +++++++++++++++++++ .../tests/teams/test_bot_config_auth.py | 14 ++++ .../tests/teams/test_config_auth_response.py | 14 ++++ .../tests/teams/test_config_response.py | 13 +++ .../tests/teams/test_config_task_response.py | 14 ++++ 8 files changed, 237 insertions(+) create mode 100644 libraries/botbuilder-schema/tests/teams/test_bot_config_auth.py create mode 100644 libraries/botbuilder-schema/tests/teams/test_config_auth_response.py create mode 100644 libraries/botbuilder-schema/tests/teams/test_config_response.py create mode 100644 libraries/botbuilder-schema/tests/teams/test_config_task_response.py diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 4fd6d4ee1..7832887dc 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -186,6 +186,22 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: ) ) + if turn_context.activity.name == "config/fetch": + return self._create_invoke_response( + await self.on_teams_config_fetch( + turn_context, + turn_context.activity.value, + ) + ) + + if turn_context.activity.name == "config/submit": + return self._create_invoke_response( + await self.on_teams_config_submit( + turn_context, + turn_context.activity.value, + ) + ) + return await super().on_invoke_activity(turn_context) except _InvokeResponseException as invoke_exception: @@ -515,6 +531,32 @@ async def on_teams_tab_submit( # pylint: disable=unused-argument """ raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + async def on_teams_config_fetch( # pylint: disable=unused-argument + self, turn_context: TurnContext, config_data: any + ): + """ + Override this in a derived class to provide logic for when a config is fetched. + + :param turn_context: A context object for this turn. + :param config_data: The config fetch invoke request value payload. + + :returns: A Config Response for the request. + """ + raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + + async def on_teams_config_submit( # pylint: disable=unused-argument + self, turn_context: TurnContext, config_data: any + ): + """ + Override this in a derived class to provide logic for when a config is submitted. + + :param turn_context: A context object for this turn. + :param config_data: The config fetch invoke request value payload. + + :returns: A Config Response for the request. + """ + raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + async def on_conversation_update_activity(self, turn_context: TurnContext): """ Invoked when a conversation update activity is received from the channel. diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index af9bcd4ab..2ea62717b 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -315,6 +315,14 @@ async def on_teams_tab_submit( self.record.append("on_teams_tab_submit") return await super().on_teams_tab_submit(turn_context, tab_submit) + async def on_teams_config_fetch(self, turn_context: TurnContext, config_data: any): + self.record.append("on_teams_config_fetch") + return await super().on_teams_config_fetch(turn_context, config_data) + + async def on_teams_config_submit(self, turn_context: TurnContext, config_data: any): + self.record.append("on_teams_config_submit") + return await super().on_teams_config_submit(turn_context, config_data) + async def on_event_activity(self, turn_context: TurnContext): self.record.append("on_event_activity") return await super().on_event_activity(turn_context) @@ -1126,6 +1134,50 @@ async def test_on_teams_tab_submit(self): assert bot.record[0] == "on_invoke_activity" assert bot.record[1] == "on_teams_tab_submit" + async def test_on_teams_config_fetch(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="config/fetch", + value={ + "data": {"key": "value", "type": "config/fetch"}, + "context": {"theme": "default"}, + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_invoke_activity" + assert bot.record[1] == "on_teams_config_fetch" + + async def test_on_teams_config_submit(self): + # Arrange + activity = Activity( + type=ActivityTypes.invoke, + name="config/submit", + value={ + "data": {"key": "value", "type": "config/submit"}, + "context": {"theme": "default"}, + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_invoke_activity" + assert bot.record[1] == "on_teams_config_submit" + async def test_on_end_of_conversation_activity(self): activity = Activity(type=ActivityTypes.end_of_conversation) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 0bac60e96..8fb944b16 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -81,6 +81,10 @@ from ._models_py3 import TeamsMeetingMember from ._models_py3 import MeetingParticipantsEventDetails from ._models_py3 import ReadReceiptInfo +from ._models_py3 import BotConfigAuth +from ._models_py3 import ConfigAuthResponse +from ._models_py3 import ConfigResponse +from ._models_py3 import ConfigTaskResponse __all__ = [ "AppBasedLinkQuery", @@ -163,4 +167,8 @@ "TeamsMeetingMember", "MeetingParticipantsEventDetails", "ReadReceiptInfo", + "BotConfigAuth", + "ConfigAuthResponse", + "ConfigResponse", + "ConfigTaskResponse", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index a507467cc..a1ab30d6c 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2619,3 +2619,83 @@ def is_message_read_instance(self, compare_message_id): return ReadReceiptInfo.is_message_read( compare_message_id, self.last_read_message_id ) + + +class BotConfigAuth(Model): + """Specifies bot config auth, including type and suggestedActions. + + :param type: The type of bot config auth. + :type type: str + :param suggested_actions: The suggested actions of bot config auth. + :type suggested_actions: ~botframework.connector.models.SuggestedActions + """ + + _attribute_map = { + "type": {"key": "type", "type": "str"}, + "suggested_actions": {"key": "suggestedActions", "type": "SuggestedActions"}, + } + + def __init__(self, *, type: str = "auth", suggested_actions=None, **kwargs) -> None: + super(BotConfigAuth, self).__init__(**kwargs) + self.type = type + self.suggested_actions = suggested_actions + + +class ConfigResponseBase(Model): + """Specifies Invoke response base, including response type. + + :param response_type: Response type for invoke request + :type response_type: str + """ + + _attribute_map = { + "response_type": {"key": "responseType", "type": "str"}, + } + + def __init__(self, *, response_type: str = None, **kwargs) -> None: + super(ConfigResponseBase, self).__init__(**kwargs) + self.response_type = response_type + + +class ConfigResponse(ConfigResponseBase): + """Envelope for Config Response Payload. + + :param config: The response to the config message. Possible values: 'auth', 'task' + :type config: T + :param cache_info: Response cache info + :type cache_info: ~botframework.connector.teams.models.CacheInfo + """ + + _attribute_map = { + "config": {"key": "config", "type": "object"}, + "cache_info": {"key": "cacheInfo", "type": "CacheInfo"}, + } + + def __init__(self, *, config=None, cache_info=None, **kwargs) -> None: + super(ConfigResponse, self).__init__(response_type="config", **kwargs) + self.config = config + self.cache_info = cache_info + + +class ConfigTaskResponse(ConfigResponse): + """Envelope for Config Task Response. + + This class uses TaskModuleResponseBase as the type for the config parameter. + """ + + def __init__(self, *, config=None, **kwargs) -> None: + super(ConfigTaskResponse, self).__init__( + config=config or TaskModuleResponseBase(), **kwargs + ) + + +class ConfigAuthResponse(ConfigResponse): + """Envelope for Config Auth Response. + + This class uses BotConfigAuth as the type for the config parameter. + """ + + def __init__(self, *, config=None, **kwargs) -> None: + super(ConfigAuthResponse, self).__init__( + config=config or BotConfigAuth(), **kwargs + ) diff --git a/libraries/botbuilder-schema/tests/teams/test_bot_config_auth.py b/libraries/botbuilder-schema/tests/teams/test_bot_config_auth.py new file mode 100644 index 000000000..f6d771c4e --- /dev/null +++ b/libraries/botbuilder-schema/tests/teams/test_bot_config_auth.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botbuilder.schema.teams import BotConfigAuth + + +class TestBotConfigAuth(aiounittest.AsyncTestCase): + def test_bot_config_auth_inits_with_no_args(self): + bot_config_auth_response = BotConfigAuth() + + self.assertIsNotNone(bot_config_auth_response) + self.assertIsInstance(bot_config_auth_response, BotConfigAuth) + self.assertEqual("auth", bot_config_auth_response.type) diff --git a/libraries/botbuilder-schema/tests/teams/test_config_auth_response.py b/libraries/botbuilder-schema/tests/teams/test_config_auth_response.py new file mode 100644 index 000000000..54221399d --- /dev/null +++ b/libraries/botbuilder-schema/tests/teams/test_config_auth_response.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botbuilder.schema.teams import ConfigAuthResponse + + +class TestConfigAuthResponse(aiounittest.AsyncTestCase): + def test_config_auth_response_init_with_no_args(self): + config_auth_response = ConfigAuthResponse() + + self.assertIsNotNone(config_auth_response) + self.assertIsInstance(config_auth_response, ConfigAuthResponse) + self.assertEqual("config", config_auth_response.response_type) diff --git a/libraries/botbuilder-schema/tests/teams/test_config_response.py b/libraries/botbuilder-schema/tests/teams/test_config_response.py new file mode 100644 index 000000000..39d2ce0d5 --- /dev/null +++ b/libraries/botbuilder-schema/tests/teams/test_config_response.py @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botbuilder.schema.teams import ConfigResponse + + +class TestConfigResponse(aiounittest.AsyncTestCase): + def test_config_response_inits_with_no_args(self): + config_response = ConfigResponse() + + self.assertIsNotNone(config_response) + self.assertIsInstance(config_response, ConfigResponse) diff --git a/libraries/botbuilder-schema/tests/teams/test_config_task_response.py b/libraries/botbuilder-schema/tests/teams/test_config_task_response.py new file mode 100644 index 000000000..53126388d --- /dev/null +++ b/libraries/botbuilder-schema/tests/teams/test_config_task_response.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import aiounittest +from botbuilder.schema.teams import ConfigTaskResponse + + +class TestConfigTaskResponse(aiounittest.AsyncTestCase): + def test_config_task_response_init_with_no_args(self): + config_task_response = ConfigTaskResponse() + + self.assertIsNotNone(config_task_response) + self.assertIsInstance(config_task_response, ConfigTaskResponse) + self.assertEqual("config", config_task_response.response_type) From d4a1867023c851517d6e5bc744bdbe5c2b61fa55 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:51:15 +0530 Subject: [PATCH 082/102] Add settings.selectedChannel to TeamsChannelData and Type to ChannelInfo and TeamDetails (#2173) --- .../botbuilder/core/teams/__init__.py | 2 + .../core/teams/teams_activity_extensions.py | 17 +++++++++ .../tests/teams/test_teams_extension.py | 30 +++++++++++++++ .../botbuilder/schema/teams/__init__.py | 2 + .../botbuilder/schema/teams/_models_py3.py | 38 ++++++++++++++++++- 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/__init__.py b/libraries/botbuilder-core/botbuilder/core/teams/__init__.py index 9d3c4d43d..7e1f1eede 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/__init__.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/__init__.py @@ -9,6 +9,7 @@ from .teams_info import TeamsInfo from .teams_activity_extensions import ( teams_get_channel_id, + teams_get_selected_channel_id, teams_get_team_info, teams_notify_user, ) @@ -19,6 +20,7 @@ "TeamsInfo", "TeamsSSOTokenExchangeMiddleware", "teams_get_channel_id", + "teams_get_selected_channel_id", "teams_get_team_info", "teams_notify_user", ] diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py index 7b9c2fd0a..04e11583f 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py @@ -31,6 +31,23 @@ def teams_get_channel_id(activity: Activity) -> str: return None +def teams_get_selected_channel_id(activity: Activity) -> str: + if not activity: + return None + + if activity.channel_data: + channel_data = TeamsChannelData().deserialize(activity.channel_data) + return ( + channel_data.settings.selected_channel.id + if channel_data + and channel_data.settings + and channel_data.settings.selected_channel + else None + ) + + return None + + def teams_get_team_info(activity: Activity) -> TeamInfo: if not activity: return None diff --git a/libraries/botbuilder-core/tests/teams/test_teams_extension.py b/libraries/botbuilder-core/tests/teams/test_teams_extension.py index 98c1ee829..ac55cb9a2 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_extension.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_extension.py @@ -7,6 +7,7 @@ from botbuilder.schema.teams import TeamInfo from botbuilder.core.teams import ( teams_get_channel_id, + teams_get_selected_channel_id, teams_get_team_info, teams_notify_user, ) @@ -26,6 +27,35 @@ def test_teams_get_channel_id(self): # Assert assert result == "id123" + def test_teams_get_selected_channel_id(self): + # Arrange + activity = Activity( + channel_data={ + "channel": {"id": "id123", "name": "channel_name"}, + "settings": { + "selectedChannel": {"id": "id12345", "name": "channel_name"} + }, + } + ) + + # Act + result = teams_get_selected_channel_id(activity) + + # Assert + assert result == "id12345" + + def test_teams_get_selected_channel_id_with_no_selected_channel(self): + # Arrange + activity = Activity( + channel_data={"channel": {"id": "id123", "name": "channel_name"}} + ) + + # Act + result = teams_get_selected_channel_id(activity) + + # Assert + assert result is None + def test_teams_get_channel_id_with_no_channel(self): # Arrange activity = Activity( diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 8fb944b16..76e34f5f8 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -59,6 +59,7 @@ from ._models_py3 import TeamDetails from ._models_py3 import TeamInfo from ._models_py3 import TeamsChannelAccount +from ._models_py3 import TeamsChannelDataSettings from ._models_py3 import TeamsChannelData from ._models_py3 import TeamsPagedMembersResult from ._models_py3 import TenantInfo @@ -145,6 +146,7 @@ "TeamDetails", "TeamInfo", "TeamsChannelAccount", + "TeamsChannelDataSettings", "TeamsChannelData", "TeamsPagedMembersResult", "TenantInfo", diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index a1ab30d6c..8b5cebd29 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -87,17 +87,23 @@ class ChannelInfo(Model): :type id: str :param name: Name of the channel :type name: str + :param type: The channel type + :type type: str """ _attribute_map = { "id": {"key": "id", "type": "str"}, "name": {"key": "name", "type": "str"}, + "type": {"key": "type", "type": "str"}, } - def __init__(self, *, id: str = None, name: str = None, **kwargs) -> None: + def __init__( + self, *, id: str = None, name: str = None, type: str = None, **kwargs + ) -> None: super(ChannelInfo, self).__init__(**kwargs) self.id = id self.name = name + self.type = type class CacheInfo(Model): @@ -1820,6 +1826,8 @@ class TeamDetails(Model): :type channel_count: int :param member_count: The count of members in the team. :type member_count: int + :param type: The team type + :type type: str """ _attribute_map = { @@ -1828,6 +1836,7 @@ class TeamDetails(Model): "aad_group_id": {"key": "aadGroupId", "type": "str"}, "channel_count": {"key": "channelCount", "type": "int"}, "member_count": {"key": "memberCount", "type": "int"}, + "type": {"key": "type", "type": "str"}, } def __init__( @@ -1838,6 +1847,7 @@ def __init__( aad_group_id: str = None, member_count: int = None, channel_count: int = None, + type: str = None, **kwargs ) -> None: super(TeamDetails, self).__init__(**kwargs) @@ -1846,6 +1856,7 @@ def __init__( self.aad_group_id = aad_group_id self.channel_count = channel_count self.member_count = member_count + self.type = type class TeamInfo(Model): @@ -1958,6 +1969,26 @@ def __init__( self.members = members +class TeamsChannelDataSettings(Model): + """ + Represents the settings information for a Teams channel data. + + :param selected_channel: Information about the selected Teams channel. + :type selected_channel: ~botframework.connector.teams.models.ChannelInfo + :param additional_properties: Gets or sets properties that are not otherwise defined by the + type but that might appear in the REST JSON object. + :type additional_properties: object + """ + + _attribute_map = { + "selected_channel": {"key": "selectedChannel", "type": "ChannelInfo"}, + } + + def __init__(self, *, selected_channel=None, **kwargs) -> None: + super(TeamsChannelDataSettings, self).__init__(**kwargs) + self.selected_channel = selected_channel + + class TeamsChannelData(Model): """Channel data specific to messages received in Microsoft Teams. @@ -1974,6 +2005,8 @@ class TeamsChannelData(Model): :type tenant: ~botframework.connector.teams.models.TenantInfo :param meeting: Information about the meeting in which the message was sent :type meeting: ~botframework.connector.teams.models.TeamsMeetingInfo + :param meeting: Information about the about the settings in which the message was sent + :type meeting: ~botframework.connector.teams.models.TeamsChannelDataSettings """ _attribute_map = { @@ -1983,6 +2016,7 @@ class TeamsChannelData(Model): "notification": {"key": "notification", "type": "NotificationInfo"}, "tenant": {"key": "tenant", "type": "TenantInfo"}, "meeting": {"key": "meeting", "type": "TeamsMeetingInfo"}, + "settings": {"key": "settings", "type": "TeamsChannelDataSettings"}, } def __init__( @@ -1994,6 +2028,7 @@ def __init__( notification=None, tenant=None, meeting=None, + settings: TeamsChannelDataSettings = None, **kwargs ) -> None: super(TeamsChannelData, self).__init__(**kwargs) @@ -2004,6 +2039,7 @@ def __init__( self.notification = notification self.tenant = tenant self.meeting = meeting + self.settings = settings class TenantInfo(Model): From 0f2cb2adb97c25670e728a714bbf667fe9aee369 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:03:44 +0530 Subject: [PATCH 083/102] Port: Send targeted meeting notification in Teams meeting (#2172) * Send targeted meeting notification in Teams meeting * test cases --------- Co-authored-by: tracyboehrer --- .../botbuilder/core/teams/teams_info.py | 27 ++ .../tests/teams/test_teams_info.py | 129 ++++++++ .../botbuilder/schema/teams/__init__.py | 4 + .../botbuilder/schema/teams/_models_py3.py | 289 ++++++++++++++++++ .../teams/operations/teams_operations.py | 73 +++++ 5 files changed, 522 insertions(+) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py index f70f6cccc..4afa50c05 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_info.py @@ -25,6 +25,8 @@ TeamsChannelAccount, TeamsPagedMembersResult, TeamsMeetingParticipant, + MeetingNotificationBase, + MeetingNotificationResponse, ) @@ -100,6 +102,31 @@ async def _legacy_send_message_to_teams_channel( ) return (result[0], result[1]) + @staticmethod + async def send_meeting_notification( + turn_context: TurnContext, + notification: MeetingNotificationBase, + meeting_id: str = None, + ) -> MeetingNotificationResponse: + meeting_id = ( + meeting_id + if meeting_id + else teams_get_meeting_info(turn_context.activity).id + ) + if meeting_id is None: + raise TypeError( + "TeamsInfo._send_meeting_notification: method requires a meeting_id or " + "TurnContext that contains a meeting id" + ) + + if notification is None: + raise TypeError("notification is required.") + + connector_client = await TeamsInfo.get_teams_connector_client(turn_context) + return await connector_client.teams.send_meeting_notification( + meeting_id, notification + ) + @staticmethod async def _create_conversation_callback( new_turn_context, diff --git a/libraries/botbuilder-core/tests/teams/test_teams_info.py b/libraries/botbuilder-core/tests/teams/test_teams_info.py index dea57030c..00f4ad8a4 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_info.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_info.py @@ -1,7 +1,19 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import json import aiounittest +from botbuilder.schema.teams._models_py3 import ( + ContentType, + MeetingNotificationChannelData, + MeetingStageSurface, + MeetingTabIconSurface, + OnBehalfOf, + TargetedMeetingNotification, + TargetedMeetingNotificationValue, + TaskModuleContinueResponse, + TaskModuleTaskInfo, +) from botframework.connector import Channels from botbuilder.core import TurnContext, MessageFactory @@ -234,6 +246,53 @@ async def test_get_meeting_info(self): handler = TeamsActivityHandler() await handler.on_turn(turn_context) + async def test_send_meeting_notificationt(self): + test_cases = [ + ("202", "accepted"), + ( + "207", + "if the notifications are sent only to parital number of recipients\ + because the validation on some recipients' ids failed or some\ + recipients were not found in the roster. In this case, \ + SMBA will return the user MRIs of those failed recipients\ + in a format that was given to a bot (ex: if a bot sent \ + encrypted user MRIs, return encrypted one).", + ), + ( + "400", + "when Meeting Notification request payload validation fails. For instance,\ + Recipients: # of recipients is greater than what the API allows ||\ + all of recipients' user ids were invalid, Surface: Surface list\ + is empty or null, Surface type is invalid, Duplicative \ + surface type exists in one payload", + ), + ( + "403", + "if the bot is not allowed to send the notification. In this case,\ + the payload should contain more detail error message. \ + There can be many reasons: bot disabled by tenant admin,\ + blocked during live site mitigation, the bot does not\ + have a correct RSC permission for a specific surface type, etc", + ), + ] + for status_code, expected_message in test_cases: + adapter = SimpleAdapterWithCreateConversation() + + activity = Activity( + type="targetedMeetingNotification", + text="Test-send_meeting_notificationt", + channel_id=Channels.ms_teams, + from_property=ChannelAccount( + aad_object_id="participantId-1", name=status_code + ), + service_url="https://test.coffee", + conversation=ConversationAccount(id="conversation-id"), + ) + + turn_context = TurnContext(adapter, activity) + handler = TeamsActivityHandler() + await handler.on_turn(turn_context) + class TestTeamsActivityHandler(TeamsActivityHandler): async def on_turn(self, turn_context: TurnContext): @@ -241,6 +300,8 @@ async def on_turn(self, turn_context: TurnContext): if turn_context.activity.text == "test_send_message_to_teams_channel": await self.call_send_message_to_teams(turn_context) + elif turn_context.activity.text == "test_send_meeting_notification": + await self.call_send_meeting_notification(turn_context) async def call_send_message_to_teams(self, turn_context: TurnContext): msg = MessageFactory.text("call_send_message_to_teams") @@ -251,3 +312,71 @@ async def call_send_message_to_teams(self, turn_context: TurnContext): assert reference[0].activity_id == "new_conversation_id" assert reference[1] == "reference123" + + async def call_send_meeting_notification(self, turn_context: TurnContext): + from_property = turn_context.activity.from_property + try: + # Send the meeting notification asynchronously + failed_participants = await TeamsInfo.send_meeting_notification( + turn_context, + self.get_targeted_meeting_notification(from_property), + "meeting-id", + ) + + # Handle based on the 'from_property.name' + if from_property.name == "207": + self.assertEqual( + "failingid", + failed_participants.recipients_failure_info[0].recipient_mri, + ) + elif from_property.name == "202": + assert failed_participants is None + else: + raise TypeError( + f"Expected HttpOperationException with response status code {from_property.name}." + ) + + except ValueError as ex: + # Assert that the response status code matches the from_property.name + assert from_property.name == str(int(ex.response.status_code)) + + # Deserialize the error response content to an ErrorResponse object + error_response = json.loads(ex.response.content) + + # Handle based on error codes + if from_property.name == "400": + assert error_response["error"]["code"] == "BadSyntax" + elif from_property.name == "403": + assert error_response["error"]["code"] == "BotNotInConversationRoster" + else: + raise TypeError( + f"Expected HttpOperationException with response status code {from_property.name}." + ) + + def get_targeted_meeting_notification(self, from_account: ChannelAccount): + recipients = [from_account.id] + + if from_account.name == "207": + recipients.append("failingid") + + meeting_stage_surface = MeetingStageSurface( + content=TaskModuleContinueResponse( + value=TaskModuleTaskInfo(title="title here", height=3, width=2) + ), + content_type=ContentType.Task, + ) + + meeting_tab_icon_surface = MeetingTabIconSurface( + tab_entity_id="test tab entity id" + ) + + value = TargetedMeetingNotificationValue( + recipients=recipients, + surfaces=[meeting_stage_surface, meeting_tab_icon_surface], + ) + + obo = OnBehalfOf(display_name=from_account.name, mri=from_account.id) + + channel_data = MeetingNotificationChannelData(on_behalf_of_list=[obo]) + + return TargetedMeetingNotification(value=value, channel_data=channel_data) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 76e34f5f8..7c837b243 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -86,6 +86,8 @@ from ._models_py3 import ConfigAuthResponse from ._models_py3 import ConfigResponse from ._models_py3 import ConfigTaskResponse +from ._models_py3 import MeetingNotificationBase +from ._models_py3 import MeetingNotificationResponse __all__ = [ "AppBasedLinkQuery", @@ -173,4 +175,6 @@ "ConfigAuthResponse", "ConfigResponse", "ConfigTaskResponse", + "MeetingNotificationBase", + "MeetingNotificationResponse", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 8b5cebd29..72f48d5b6 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from enum import Enum from typing import List from msrest.serialization import Model from botbuilder.schema import ( @@ -2735,3 +2736,291 @@ def __init__(self, *, config=None, **kwargs) -> None: super(ConfigAuthResponse, self).__init__( config=config or BotConfigAuth(), **kwargs ) + + +class OnBehalfOf(Model): + """Specifies attribution for notifications. + + :param item_id: The identification of the item. Default is 0. + :type item_id: int + :param mention_type: The mention type. Default is "person". + :type mention_type: str + :param mri: Message resource identifier (MRI) of the person on whose behalf the message is sent. + :type mri: str + :param display_name: Name of the person. Used as fallback in case name resolution is unavailable. + :type display_name: str + """ + + _attribute_map = { + "item_id": {"key": "itemid", "type": "int"}, + "mention_type": {"key": "mentionType", "type": "str"}, + "mri": {"key": "mri", "type": "str"}, + "display_name": {"key": "displayName", "type": "str"}, + } + + def __init__( + self, + *, + item_id: int = 0, + mention_type: str = "person", + mri: str = None, + display_name: str = None, + **kwargs + ) -> None: + super(OnBehalfOf, self).__init__(**kwargs) + self.item_id = item_id + self.mention_type = mention_type + self.mri = mri + self.display_name = display_name + + +class SurfaceType(Enum): + """ + Defines Teams Surface type for use with a Surface object. + + :var Unknown: TeamsSurfaceType is Unknown. + :vartype Unknown: int + :var MeetingStage: TeamsSurfaceType is MeetingStage.. + :vartype MeetingStage: int + :var MeetingTabIcon: TeamsSurfaceType is MeetingTabIcon. + :vartype MeetingTabIcon: int + """ + + Unknown = 0 + + MeetingStage = 1 + + MeetingTabIcon = 2 + + +class ContentType(Enum): + """ + Defines content type. Depending on contentType, content field will have a different structure. + + :var Unknown: Content type is Unknown. + :vartype Unknown: int + :var Task: TContent type is Task. + :vartype Task: int + """ + + Unknown = 0 + + Task = 1 + + +class MeetingNotificationBase(Model): + """Specifies Bot meeting notification base including channel data and type. + + :param type: Type of Bot meeting notification. + :type type: str + """ + + _attribute_map = { + "type": {"key": "type", "type": "str"}, + } + + def __init__(self, *, type: str = None, **kwargs) -> None: + super(MeetingNotificationBase, self).__init__(**kwargs) + self.type = type + + +class MeetingNotification(MeetingNotificationBase): + """Specifies Bot meeting notification including meeting notification value. + + :param value: Teams Bot meeting notification value. + :type value: TargetedMeetingNotificationValue + """ + + _attribute_map = { + "value": {"key": "value", "type": "TargetedMeetingNotificationValue"}, + } + + def __init__( + self, *, value: "TargetedMeetingNotificationValue" = None, **kwargs + ) -> None: + super(MeetingNotification, self).__init__(**kwargs) + self.value = value + + +class MeetingNotificationChannelData(Model): + """Specify Teams Bot meeting notification channel data. + + :param on_behalf_of_list: The Teams Bot meeting notification's OnBehalfOf list. + :type on_behalf_of_list: list[~botframework.connector.teams.models.OnBehalfOf] + """ + + _attribute_map = { + "on_behalf_of_list": {"key": "OnBehalfOf", "type": "[OnBehalfOf]"} + } + + def __init__(self, *, on_behalf_of_list: List["OnBehalfOf"] = None, **kwargs): + super(MeetingNotificationChannelData, self).__init__(**kwargs) + self.on_behalf_of_list = on_behalf_of_list + + +class MeetingNotificationRecipientFailureInfo(Model): + """Information regarding failure to notify a recipient of a meeting notification. + + :param recipient_mri: The MRI for a recipient meeting notification failure. + :type recipient_mri: str + :param error_code: The error code for a meeting notification. + :type error_code: str + :param failure_reason: The reason why a participant meeting notification failed. + :type failure_reason: str + """ + + _attribute_map = { + "recipient_mri": {"key": "recipientMri", "type": "str"}, + "error_code": {"key": "errorcode", "type": "str"}, + "failure_reason": {"key": "failureReason", "type": "str"}, + } + + def __init__( + self, + *, + recipient_mri: str = None, + error_code: str = None, + failure_reason: str = None, + **kwargs + ): + super(MeetingNotificationRecipientFailureInfo, self).__init__(**kwargs) + self.recipient_mri = recipient_mri + self.error_code = error_code + self.failure_reason = failure_reason + + +class MeetingNotificationResponse(Model): + """Specifies Bot meeting notification response. + + Contains list of MeetingNotificationRecipientFailureInfo. + + :param recipients_failure_info: The list of MeetingNotificationRecipientFailureInfo. + :type recipients_failure_info: list[~botframework.connector.teams.models.MeetingNotificationRecipientFailureInfo] + """ + + _attribute_map = { + "recipients_failure_info": { + "key": "recipientsFailureInfo", + "type": "[MeetingNotificationRecipientFailureInfo]", + } + } + + def __init__( + self, + *, + recipients_failure_info: List["MeetingNotificationRecipientFailureInfo"] = None, + **kwargs + ): + super(MeetingNotificationResponse, self).__init__(**kwargs) + self.recipients_failure_info = recipients_failure_info + + +class Surface(Model): + """Specifies where the notification will be rendered in the meeting UX. + + :param type: The value indicating where the notification will be rendered in the meeting UX. + :type type: ~botframework.connector.teams.models.SurfaceType + """ + + _attribute_map = { + "type": {"key": "surface", "type": "SurfaceType"}, + } + + def __init__(self, *, type: SurfaceType = None, **kwargs): + super(Surface, self).__init__(**kwargs) + self.type = type + + +class MeetingStageSurface(Surface): + """Specifies meeting stage surface. + + :param content_type: The content type of this MeetingStageSurface. + :type content_type: ~botframework.connector.teams.models.ContentType + :param content: The content of this MeetingStageSurface. + :type content: object + """ + + _attribute_map = { + "content_type": {"key": "contentType", "type": "ContentType"}, + "content": {"key": "content", "type": "object"}, + } + + def __init__( + self, + *, + content_type: ContentType = ContentType.Task, + content: object = None, + **kwargs + ): + super(MeetingStageSurface, self).__init__(SurfaceType.MeetingStage, **kwargs) + self.content_type = content_type + self.content = content + + +class MeetingTabIconSurface(Surface): + """ + Specifies meeting tab icon surface. + + :param tab_entity_id: The tab entity Id of this MeetingTabIconSurface. + :type tab_entity_id: str + """ + + _attribute_map = { + "tab_entity_id": {"key": "tabEntityId", "type": "str"}, + } + + def __init__(self, *, tab_entity_id: str = None, **kwargs): + super(MeetingTabIconSurface, self).__init__( + SurfaceType.MeetingTabIcon, **kwargs + ) + self.tab_entity_id = tab_entity_id + + +class TargetedMeetingNotificationValue(Model): + """Specifies the targeted meeting notification value, including recipients and surfaces. + + :param recipients: The collection of recipients of the targeted meeting notification. + :type recipients: list[str] + :param surfaces: The collection of surfaces on which to show the notification. + :type surfaces: list[~botframework.connector.teams.models.Surface] + """ + + _attribute_map = { + "recipients": {"key": "recipients", "type": "[str]"}, + "surfaces": {"key": "surfaces", "type": "[Surface]"}, + } + + def __init__( + self, *, recipients: List[str] = None, surfaces: List[Surface] = None, **kwargs + ): + super(TargetedMeetingNotificationValue, self).__init__(**kwargs) + self.recipients = recipients + self.surfaces = surfaces + + +class TargetedMeetingNotification(MeetingNotification): + """Specifies Teams targeted meeting notification. + + :param value: The value of the TargetedMeetingNotification. + :type value: ~botframework.connector.teams.models.TargetedMeetingNotificationValue + :param channel_data: Teams Bot meeting notification channel data. + :type channel_data: ~botframework.connector.teams.models.MeetingNotificationChannelData + """ + + _attribute_map = { + "value": {"key": "value", "type": "TargetedMeetingNotificationValue"}, + "channel_data": { + "key": "channelData", + "type": "MeetingNotificationChannelData", + }, + } + + def __init__( + self, + *, + value: "TargetedMeetingNotificationValue" = None, + channel_data: "MeetingNotificationChannelData" = None, + **kwargs + ): + super(TargetedMeetingNotification, self).__init__(value=value, **kwargs) + self.channel_data = channel_data diff --git a/libraries/botframework-connector/botframework/connector/teams/operations/teams_operations.py b/libraries/botframework-connector/botframework/connector/teams/operations/teams_operations.py index ff1bdb18c..6e453ae23 100644 --- a/libraries/botframework-connector/botframework/connector/teams/operations/teams_operations.py +++ b/libraries/botframework-connector/botframework/connector/teams/operations/teams_operations.py @@ -266,3 +266,76 @@ def fetch_meeting( return deserialized fetch_participant.metadata = {"url": "/https/github.com/v1/meetings/{meetingId}"} + + def send_meeting_notification( + self, + meeting_id: str, + notification: models.MeetingNotificationBase, + custom_headers=None, + raw=False, + **operation_config + ): + """Send a teams meeting notification. + + :param meeting_id: Meeting Id, encoded as a BASE64 string. + :type meeting_id: str + :param notification: The notification to send to Teams + :type notification: ~botframework.connector.teams.models.MeetingNotificationBase + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: MeetingNotificationResponse or ClientRawResponse if raw=true + :rtype: ~botframework.connector.teams.models.MeetingNotificationResponse or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`HttpOperationError` + """ + + # Construct URL + url = self.send_meeting_notification.metadata["url"] + path_format_arguments = { + "meetingId": self._serialize.url("meeting_id", meeting_id, "str"), + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters["Accept"] = "application/json" + header_parameters["Content-Type"] = "application/json; charset=utf-8" + if custom_headers: + header_parameters.update(custom_headers) + + # Construct body + body_content = self._serialize.body(notification, "notification") + + # Construct and send request + request = self._client.post( + url, query_parameters, header_parameters, body_content + ) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 201, 202]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + if response.status_code == 200: + deserialized = self._deserialize("MeetingNotificationResponse", response) + if response.status_code == 201: + deserialized = self._deserialize("MeetingNotificationResponse", response) + if response.status_code == 202: + deserialized = self._deserialize("MeetingNotificationResponse", response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + send_meeting_notification.metadata = { + "url": "/https/github.com/v1/meetings/{meetingId}/notification" + } From 1617885d2e710af888cc37b97ae61c53306e4355 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:22:42 +0530 Subject: [PATCH 084/102] Port: TeamsChannelData need OnBehalfOf [#6609] (#2175) * Adds the OnBehalfOf property to send messages via bots on behalf of another user in Teams. * black issue fix * Using List from typing --- .../core/teams/teams_activity_extensions.py | 13 +++++ .../tests/teams/test_teams_channel_data.py | 56 +++++++++++++++++++ .../tests/teams/test_teams_extension.py | 27 ++++++++- .../botbuilder/schema/teams/__init__.py | 2 + .../botbuilder/schema/teams/_models_py3.py | 9 ++- 5 files changed, 104 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py index 04e11583f..e604cb088 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py @@ -1,12 +1,14 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from typing import List from botbuilder.schema import Activity from botbuilder.schema.teams import ( NotificationInfo, TeamsChannelData, TeamInfo, TeamsMeetingInfo, + OnBehalfOf, ) @@ -84,3 +86,14 @@ def teams_get_meeting_info(activity: Activity) -> TeamsMeetingInfo: return channel_data.meeting return None + + +def teams_get_team_on_behalf_of(activity: Activity) -> List[OnBehalfOf]: + if not activity: + return None + + if activity.channel_data: + channel_data = TeamsChannelData().deserialize(activity.channel_data) + return channel_data.on_behalf_of + + return None diff --git a/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py b/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py index e468526bc..324749ce5 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_channel_data.py @@ -1,11 +1,21 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from uuid import uuid4 import aiounittest from botbuilder.schema import Activity from botbuilder.schema.teams import TeamsChannelData from botbuilder.core.teams import teams_get_team_info +from botbuilder.schema.teams._models_py3 import ( + ChannelInfo, + NotificationInfo, + OnBehalfOf, + TeamInfo, + TeamsChannelDataSettings, + TeamsMeetingInfo, + TenantInfo, +) class TestTeamsChannelData(aiounittest.AsyncTestCase): @@ -28,3 +38,49 @@ def test_teams_get_team_info(self): # Assert assert team_info.aad_group_id == "teamGroup123" + + def test_teams_channel_data_inits(self): + # Arrange + channel = ChannelInfo(id="general", name="General") + event_type = "eventType" + team = TeamInfo(id="supportEngineers", name="Support Engineers") + notification = NotificationInfo(alert=True) + tenant = TenantInfo(id="uniqueTenantId") + meeting = TeamsMeetingInfo(id="BFSE Stand Up") + settings = TeamsChannelDataSettings(selected_channel=channel) + on_behalf_of = [ + OnBehalfOf( + display_name="onBehalfOfTest", + item_id=0, + mention_type="person", + mri=str(uuid4()), + ) + ] + + # Act + channel_data = TeamsChannelData( + channel=channel, + event_type=event_type, + team=team, + notification=notification, + tenant=tenant, + meeting=meeting, + settings=settings, + on_behalf_of=on_behalf_of, + ) + + # Assert + self.assertIsNotNone(channel_data) + self.assertIsInstance(channel_data, TeamsChannelData) + self.assertEqual(channel, channel_data.channel) + self.assertEqual(event_type, channel_data.event_type) + self.assertEqual(team, channel_data.team) + self.assertEqual(notification, channel_data.notification) + self.assertEqual(tenant, channel_data.tenant) + self.assertEqual(meeting, channel_data.meeting) + self.assertEqual(settings, channel_data.settings) + self.assertEqual(on_behalf_of, channel_data.on_behalf_of) + self.assertEqual(on_behalf_of[0].display_name, "onBehalfOfTest") + self.assertEqual(on_behalf_of[0].mention_type, "person") + self.assertIsNotNone(on_behalf_of[0].mri) + self.assertEqual(on_behalf_of[0].item_id, 0) diff --git a/libraries/botbuilder-core/tests/teams/test_teams_extension.py b/libraries/botbuilder-core/tests/teams/test_teams_extension.py index ac55cb9a2..1d2520bc4 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_extension.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_extension.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from uuid import uuid4 import aiounittest from botbuilder.schema import Activity @@ -11,7 +12,11 @@ teams_get_team_info, teams_notify_user, ) -from botbuilder.core.teams.teams_activity_extensions import teams_get_meeting_info +from botbuilder.core.teams.teams_activity_extensions import ( + teams_get_meeting_info, + teams_get_team_on_behalf_of, +) +from botbuilder.schema.teams._models_py3 import OnBehalfOf class TestTeamsActivityHandler(aiounittest.AsyncTestCase): @@ -190,3 +195,23 @@ def test_teams_meeting_info(self): # Assert assert meeting_id == "meeting123" + + def test_teams_channel_data_existing_on_behalf_of(self): + # Arrange + on_behalf_of_list = [ + OnBehalfOf( + display_name="onBehalfOfTest", + item_id=0, + mention_type="person", + mri=str(uuid4()), + ) + ] + + activity = Activity(channel_data={"onBehalfOf": on_behalf_of_list}) + + # Act + on_behalf_of_list = teams_get_team_on_behalf_of(activity) + + # Assert + self.assertEqual(1, len(on_behalf_of_list)) + self.assertEqual("onBehalfOfTest", on_behalf_of_list[0].display_name) diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 7c837b243..be9aa11ce 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -88,6 +88,7 @@ from ._models_py3 import ConfigTaskResponse from ._models_py3 import MeetingNotificationBase from ._models_py3 import MeetingNotificationResponse +from ._models_py3 import OnBehalfOf __all__ = [ "AppBasedLinkQuery", @@ -177,4 +178,5 @@ "ConfigTaskResponse", "MeetingNotificationBase", "MeetingNotificationResponse", + "OnBehalfOf", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 72f48d5b6..0b6e0e899 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2006,8 +2006,10 @@ class TeamsChannelData(Model): :type tenant: ~botframework.connector.teams.models.TenantInfo :param meeting: Information about the meeting in which the message was sent :type meeting: ~botframework.connector.teams.models.TeamsMeetingInfo - :param meeting: Information about the about the settings in which the message was sent - :type meeting: ~botframework.connector.teams.models.TeamsChannelDataSettings + :param settings: Information about the about the settings in which the message was sent + :type settings: ~botframework.connector.teams.models.TeamsChannelDataSettings + :param on_behalf_of: The OnBehalfOf list for user attribution + :type on_behalf_of: list[~botframework.connector.teams.models.OnBehalfOf] """ _attribute_map = { @@ -2018,6 +2020,7 @@ class TeamsChannelData(Model): "tenant": {"key": "tenant", "type": "TenantInfo"}, "meeting": {"key": "meeting", "type": "TeamsMeetingInfo"}, "settings": {"key": "settings", "type": "TeamsChannelDataSettings"}, + "on_behalf_of": {"key": "onBehalfOf", "type": "[OnBehalfOf]"}, } def __init__( @@ -2030,6 +2033,7 @@ def __init__( tenant=None, meeting=None, settings: TeamsChannelDataSettings = None, + on_behalf_of: List["OnBehalfOf"] = None, **kwargs ) -> None: super(TeamsChannelData, self).__init__(**kwargs) @@ -2041,6 +2045,7 @@ def __init__( self.tenant = tenant self.meeting = meeting self.settings = settings + self.on_behalf_of = on_behalf_of if on_behalf_of is not None else [] class TenantInfo(Model): From cf1d0453af207a239333c4f0da4ca484e1b78244 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:47:00 +0530 Subject: [PATCH 085/102] port: SkillDialog.InterceptOAuthCardsAsync doesn't support CloudAdapter (#6848) (#2179) * Updated the InterceptOAuthCardsAsync method in SkillDialog to support CloudAdapter in combination with expect replies delivery mode * minor fix --- .../botbuilder/dialogs/skills/skill_dialog.py | 81 ++++++++++--------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py index e5be50c24..d848c13c7 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/skills/skill_dialog.py @@ -26,6 +26,8 @@ from .begin_skill_dialog_options import BeginSkillDialogOptions from .skill_dialog_options import SkillDialogOptions +from botbuilder.dialogs.prompts import OAuthPromptSettings +from .._user_token_access import _UserTokenAccess class SkillDialog(Dialog): @@ -275,50 +277,55 @@ async def _intercept_oauth_cards( """ Tells is if we should intercept the OAuthCard message. """ - if not connection_name or not isinstance( - context.adapter, ExtendedUserTokenProvider - ): + if not connection_name or connection_name.isspace(): # The adapter may choose not to support token exchange, in which case we fallback to # showing an oauth card to the user. return False oauth_card_attachment = next( - attachment - for attachment in activity.attachments - if attachment.content_type == ContentTypes.oauth_card + ( + attachment + for attachment in activity.attachments + if attachment.content_type == ContentTypes.oauth_card + ), + None, ) - if oauth_card_attachment: - oauth_card = oauth_card_attachment.content - if ( - oauth_card - and oauth_card.token_exchange_resource - and oauth_card.token_exchange_resource.uri - ): - try: - result = await context.adapter.exchange_token( - turn_context=context, - connection_name=connection_name, - user_id=context.activity.from_property.id, - exchange_request=TokenExchangeRequest( - uri=oauth_card.token_exchange_resource.uri - ), - ) + if oauth_card_attachment is None: + return False - if result and result.token: - # If token above is null, then SSO has failed and hence we return false. - # If not, send an invoke to the skill with the token. - return await self._send_token_exchange_invoke_to_skill( - activity, - oauth_card.token_exchange_resource.id, - oauth_card.connection_name, - result.token, - ) - except: - # Failures in token exchange are not fatal. They simply mean that the user needs - # to be shown the OAuth card. - return False - - return False + oauth_card = oauth_card_attachment.content + if ( + not oauth_card + or not oauth_card.token_exchange_resource + or not oauth_card.token_exchange_resource.uri + ): + return False + + try: + settings = OAuthPromptSettings( + connection_name=connection_name, title="Sign In" + ) + result = await _UserTokenAccess.exchange_token( + context, + settings, + TokenExchangeRequest(uri=oauth_card.token_exchange_resource.uri), + ) + + if not result or not result.token: + # If token above is null, then SSO has failed and hence we return false. + return False + + # If not, send an invoke to the skill with the token. + return await self._send_token_exchange_invoke_to_skill( + activity, + oauth_card.token_exchange_resource.id, + oauth_card.connection_name, + result.token, + ) + except: + # Failures in token exchange are not fatal. They simply mean that the user needs + # to be shown the OAuth card. + return False async def _send_token_exchange_invoke_to_skill( self, From 3e76e543477500b7e56f53ba20258b730ec4f924 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:26:51 +0530 Subject: [PATCH 086/102] Add missing paran in exchange_token() call (#2182) --- .../botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py index 0aa005789..ba25a0baa 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/_user_token_access.py @@ -104,6 +104,7 @@ async def exchange_token( channel_id = turn_context.activity.channel_id return await user_token_client.exchange_token( user_id, + settings.connection_name, channel_id, token_exchange_request, ) From a74532b9a92234d9b2a4bcf54e133d0d024331d9 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:20:11 +0530 Subject: [PATCH 087/102] Support Teams message edit, message soft delete, message undelete activities (#2183) Co-authored-by: tracyboehrer --- .../botbuilder/core/activity_handler.py | 32 ++++ .../core/teams/teams_activity_handler.py | 70 +++++++++ .../teams/test_teams_activity_handler.py | 139 ++++++++++++++++++ .../tests/test_activity_handler.py | 34 +++++ 4 files changed, 275 insertions(+) diff --git a/libraries/botbuilder-core/botbuilder/core/activity_handler.py b/libraries/botbuilder-core/botbuilder/core/activity_handler.py index be847739e..51aefb245 100644 --- a/libraries/botbuilder-core/botbuilder/core/activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/activity_handler.py @@ -68,6 +68,10 @@ async def on_turn( if turn_context.activity.type == ActivityTypes.message: await self.on_message_activity(turn_context) + elif turn_context.activity.type == ActivityTypes.message_update: + await self.on_message_update_activity(turn_context) + elif turn_context.activity.type == ActivityTypes.message_delete: + await self.on_message_delete_activity(turn_context) elif turn_context.activity.type == ActivityTypes.conversation_update: await self.on_conversation_update_activity(turn_context) elif turn_context.activity.type == ActivityTypes.message_reaction: @@ -107,6 +111,34 @@ async def on_message_activity( # pylint: disable=unused-argument """ return + async def on_message_update_activity( # pylint: disable=unused-argument + self, turn_context: TurnContext + ): + """ + Override this method in a derived class to provide logic specific to activities, + such as the conversational logic. + + :param turn_context: The context object for this turn + :type turn_context: :class:`botbuilder.core.TurnContext` + + :returns: A task that represents the work queued to execute + """ + return + + async def on_message_delete_activity( # pylint: disable=unused-argument + self, turn_context: TurnContext + ): + """ + Override this method in a derived class to provide logic specific to activities, + such as the conversational logic. + + :param turn_context: The context object for this turn + :type turn_context: :class:`botbuilder.core.TurnContext` + + :returns: A task that represents the work queued to execute + """ + return + async def on_conversation_update_activity(self, turn_context: TurnContext): """ Invoked when a conversation update activity is received from the channel when the base behavior of diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 7832887dc..4b7558e6c 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -1042,3 +1042,73 @@ async def on_teams_meeting_participants_leave_event( :returns: A task that represents the work queued to execute. """ return + + async def on_message_update_activity(self, turn_context: TurnContext): + """ + Invoked when a message update activity is received, such as a message edit or undelete. + + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + if turn_context.activity.channel_id == Channels.ms_teams: + channel_data = TeamsChannelData().deserialize( + turn_context.activity.channel_data + ) + + if channel_data: + if channel_data.event_type == "editMessage": + return await self.on_teams_message_edit(turn_context) + if channel_data.event_type == "undeleteMessage": + return await self.on_teams_message_undelete(turn_context) + + return await super().on_message_update_activity(turn_context) + + async def on_message_delete_activity(self, turn_context: TurnContext): + """ + Invoked when a message delete activity is received, such as a soft delete message. + + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + if turn_context.activity.channel_id == Channels.ms_teams: + channel_data = TeamsChannelData().deserialize( + turn_context.activity.channel_data + ) + + if channel_data: + if channel_data.event_type == "softDeleteMessage": + return await self.on_teams_message_soft_delete(turn_context) + + return await super().on_message_delete_activity(turn_context) + + async def on_teams_message_edit(self, turn_context: TurnContext): + """ + Invoked when a Teams edit message event activity is received. + + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return + + async def on_teams_message_undelete(self, turn_context: TurnContext): + """ + Invoked when a Teams undo soft delete message event activity is received. + + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return + + async def on_teams_message_soft_delete(self, turn_context: TurnContext): + """ + Invoked when a Teams soft delete message event activity is received. + + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 2ea62717b..bef959d14 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -34,6 +34,7 @@ TabContext, MeetingParticipantsEventDetails, ReadReceiptInfo, + TeamsChannelData, ) from botframework.connector import Channels from simple_adapter import SimpleAdapter @@ -351,6 +352,26 @@ async def on_teams_meeting_end_event( turn_context.activity.value, turn_context ) + async def on_message_update_activity(self, turn_context: TurnContext): + self.record.append("on_message_update_activity") + return await super().on_message_update_activity(turn_context) + + async def on_teams_message_edit(self, turn_context: TurnContext): + self.record.append("on_teams_message_edit") + return await super().on_teams_message_edit(turn_context) + + async def on_teams_message_undelete(self, turn_context: TurnContext): + self.record.append("on_teams_message_undelete") + return await super().on_teams_message_undelete(turn_context) + + async def on_message_delete_activity(self, turn_context: TurnContext): + self.record.append("on_message_delete_activity") + return await super().on_message_delete_activity(turn_context) + + async def on_teams_message_soft_delete(self, turn_context: TurnContext): + self.record.append("on_teams_message_soft_delete") + return await super().on_teams_message_soft_delete(turn_context) + async def on_teams_meeting_participants_join_event( self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext ): @@ -1254,6 +1275,124 @@ async def test_on_teams_meeting_end_event(self): assert bot.record[0] == "on_event_activity" assert bot.record[1] == "on_teams_meeting_end_event" + async def test_message_update_activity_teams_message_edit(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_update, + channel_data=TeamsChannelData(event_type="editMessage"), + channel_id=Channels.ms_teams, + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(2, len(bot.record)) + self.assertEqual("on_message_update_activity", bot.record[0]) + self.assertEqual("on_teams_message_edit", bot.record[1]) + + async def test_message_update_activity_teams_message_undelete(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_update, + channel_data=TeamsChannelData(event_type="undeleteMessage"), + channel_id=Channels.ms_teams, + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(2, len(bot.record)) + self.assertEqual("on_message_update_activity", bot.record[0]) + self.assertEqual("on_teams_message_undelete", bot.record[1]) + + async def test_message_update_activity_teams_message_undelete_no_msteams(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_update, + channel_data=TeamsChannelData(event_type="undeleteMessage"), + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(1, len(bot.record)) + self.assertEqual("on_message_update_activity", bot.record[0]) + + async def test_message_update_activity_teams_no_channel_data(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_update, + channel_id=Channels.ms_teams, + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(1, len(bot.record)) + self.assertEqual("on_message_update_activity", bot.record[0]) + + async def test_message_delete_activity_teams_message_soft_delete(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_delete, + channel_data=TeamsChannelData(event_type="softDeleteMessage"), + channel_id=Channels.ms_teams, + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(2, len(bot.record)) + self.assertEqual("on_message_delete_activity", bot.record[0]) + self.assertEqual("on_teams_message_soft_delete", bot.record[1]) + + async def test_message_delete_activity_teams_message_soft_delete_no_msteams(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_delete, + channel_data=TeamsChannelData(event_type="softDeleteMessage"), + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(1, len(bot.record)) + self.assertEqual("on_message_delete_activity", bot.record[0]) + + async def test_message_delete_activity_teams_no_channel_data(self): + # Arrange + activity = Activity( + type=ActivityTypes.message_delete, + channel_id=Channels.ms_teams, + ) + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + self.assertEqual(1, len(bot.record)) + self.assertEqual("on_message_delete_activity", bot.record[0]) + async def test_on_teams_meeting_participants_join_event(self): # arrange activity = Activity( diff --git a/libraries/botbuilder-core/tests/test_activity_handler.py b/libraries/botbuilder-core/tests/test_activity_handler.py index fedc03e96..1ee0c5414 100644 --- a/libraries/botbuilder-core/tests/test_activity_handler.py +++ b/libraries/botbuilder-core/tests/test_activity_handler.py @@ -26,6 +26,14 @@ async def on_message_activity(self, turn_context: TurnContext): self.record.append("on_message_activity") return await super().on_message_activity(turn_context) + async def on_message_update_activity(self, turn_context: TurnContext): + self.record.append("on_message_update_activity") + return await super().on_message_update_activity(turn_context) + + async def on_message_delete_activity(self, turn_context: TurnContext): + self.record.append("on_message_delete_activity") + return await super().on_message_delete_activity(turn_context) + async def on_members_added_activity( self, members_added: ChannelAccount, turn_context: TurnContext ): @@ -208,6 +216,32 @@ async def test_invoke_should_not_match(self): assert bot.record[0] == "on_invoke_activity" assert adapter.activity.value.status == int(HTTPStatus.NOT_IMPLEMENTED) + async def test_on_message_update_activity(self): + activity = Activity(type=ActivityTypes.message_update) + + adapter = TestInvokeAdapter() + turn_context = TurnContext(adapter, activity) + + # Act + bot = TestingActivityHandler() + await bot.on_turn(turn_context) + + assert len(bot.record) == 1 + assert bot.record[0] == "on_message_update_activity" + + async def test_on_message_delete_activity(self): + activity = Activity(type=ActivityTypes.message_delete) + + adapter = TestInvokeAdapter() + turn_context = TurnContext(adapter, activity) + + # Act + bot = TestingActivityHandler() + await bot.on_turn(turn_context) + + assert len(bot.record) == 1 + assert bot.record[0] == "on_message_delete_activity" + async def test_on_end_of_conversation_activity(self): activity = Activity(type=ActivityTypes.end_of_conversation) From a9298c877b039c74810368a9a53cc92854d1bde3 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:07:55 +0530 Subject: [PATCH 088/102] Add new handler for anonymous query link invoke (#2184) Co-authored-by: tracyboehrer --- .../core/teams/teams_activity_handler.py | 23 ++++++++++++++++ .../teams/test_teams_activity_handler.py | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 4b7558e6c..9344e343e 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -90,6 +90,16 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: ) ) + if turn_context.activity.name == "composeExtension/anonymousQueryLink": + return self._create_invoke_response( + await self.on_teams_anonymous_app_based_link_query( + turn_context, + deserializer_helper( + AppBasedLinkQuery, turn_context.activity.value + ), + ) + ) + if turn_context.activity.name == "composeExtension/query": return self._create_invoke_response( await self.on_teams_messaging_extension_query( @@ -331,6 +341,19 @@ async def on_teams_app_based_link_query( # pylint: disable=unused-argument """ raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + async def on_teams_anonymous_app_based_link_query( # pylint: disable=unused-argument + self, turn_context: TurnContext, query: AppBasedLinkQuery + ) -> MessagingExtensionResponse: + """ + Invoked when an anonymous app based link query activity is received from the connector. + + :param turn_context: A context object for this turn. + :param query: The invoke request body type for app-based link query. + + :returns: The Messaging Extension Response for the query. + """ + raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + async def on_teams_messaging_extension_query( # pylint: disable=unused-argument self, turn_context: TurnContext, query: MessagingExtensionQuery ) -> MessagingExtensionResponse: diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index bef959d14..22550764e 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -220,6 +220,14 @@ async def on_teams_messaging_extension_query( self.record.append("on_teams_messaging_extension_query") return await super().on_teams_messaging_extension_query(turn_context, query) + async def on_teams_anonymous_app_based_link_query( + self, turn_context: TurnContext, query: AppBasedLinkQuery + ): + self.record.append("on_teams_anonymous_app_based_link_query") + return await super().on_teams_anonymous_app_based_link_query( + turn_context, query + ) + async def on_teams_messaging_extension_submit_action_dispatch( self, turn_context: TurnContext, action: MessagingExtensionAction ): @@ -837,6 +845,25 @@ async def test_on_app_based_link_query(self): assert bot.record[0] == "on_invoke_activity" assert bot.record[1] == "on_teams_messaging_extension_query" + async def test_compose_extension_anonymous_query_link(self): + # arrange + activity = Activity( + type=ActivityTypes.invoke, + name="composeExtension/anonymousQueryLink", + value={"url": "http://www.test.com"}, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_invoke_activity" + assert bot.record[1] == "on_teams_anonymous_app_based_link_query" + async def test_on_teams_messaging_extension_bot_message_preview_edit_activity(self): # Arrange From 62d736622a2814c1b090d39a73fb738ea59afadd Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:46:38 +0530 Subject: [PATCH 089/102] Set TeamsNotifyUser alert to opposite of alert_in_meeting (#2185) Co-authored-by: tracyboehrer --- .../core/teams/teams_activity_extensions.py | 2 +- .../tests/teams/test_teams_extension.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py index e604cb088..253b31f5c 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py @@ -71,7 +71,7 @@ def teams_notify_user( activity.channel_data = {} channel_data = TeamsChannelData().deserialize(activity.channel_data) - channel_data.notification = NotificationInfo(alert=True) + channel_data.notification = NotificationInfo(alert=not alert_in_meeting) channel_data.notification.alert_in_meeting = alert_in_meeting channel_data.notification.external_resource_url = external_resource_url activity.channel_data = channel_data diff --git a/libraries/botbuilder-core/tests/teams/test_teams_extension.py b/libraries/botbuilder-core/tests/teams/test_teams_extension.py index 1d2520bc4..406d3cb39 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_extension.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_extension.py @@ -155,6 +155,17 @@ def test_teams_notify_user(self): # Assert assert activity.channel_data.notification.alert + def test_teams_notify_user_alert_in_meeting(self): + # Arrange + activity = Activity() + + # Act + teams_notify_user(activity, alert_in_meeting=True) + + # Assert + assert activity.channel_data.notification.alert_in_meeting is True + assert activity.channel_data.notification.alert is False + def test_teams_notify_user_with_no_activity(self): # Arrange activity = None From 3a55f8a8e97b04900af7bd81e28f22c4a5334850 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:29:50 +0530 Subject: [PATCH 090/102] Added missing parameters: appType, UMSIName, UMSIResourceGroupName, and tenantId to the ARM template for Python bot deployment. (#2191) --- ...ameters-for-template-AzureBot-with-rg.json | 12 +++ ...arameters-for-template-BotApp-with-rg.json | 14 ++- .../deployUseExistResourceGroup/readme.md | 58 ++++++++----- .../template-AzureBot-with-rg.json | 64 +++++++++++++- .../template-BotApp-with-rg.json | 85 +++++++++++++++++-- ...rameters-for-template-AzureBot-new-rg.json | 12 +++ ...parameters-for-template-BotApp-new-rg.json | 12 +++ .../deployWithNewResourceGroup/readme.md | 54 +++++++----- .../template-AzureBot-new-rg.json | 40 ++++++++- .../template-BotApp-new-rg.json | 76 ++++++++++++++++- ...ameters-for-template-AzureBot-with-rg.json | 12 +++ ...arameters-for-template-BotApp-with-rg.json | 14 ++- .../deployUseExistResourceGroup/readme.md | 58 ++++++++----- .../template-AzureBot-with-rg.json | 64 +++++++++++++- .../template-BotApp-with-rg.json | 85 +++++++++++++++++-- ...rameters-for-template-AzureBot-new-rg.json | 12 +++ ...parameters-for-template-BotApp-new-rg.json | 12 +++ .../deployWithNewResourceGroup/readme.md | 54 +++++++----- .../template-AzureBot-new-rg.json | 40 ++++++++- .../template-BotApp-new-rg.json | 76 ++++++++++++++++- ...ameters-for-template-AzureBot-with-rg.json | 12 +++ ...arameters-for-template-BotApp-with-rg.json | 14 ++- .../deployUseExistResourceGroup/readme.md | 58 ++++++++----- .../template-AzureBot-with-rg.json | 64 +++++++++++++- .../template-BotApp-with-rg.json | 83 ++++++++++++++++-- ...rameters-for-template-AzureBot-new-rg.json | 12 +++ ...parameters-for-template-BotApp-new-rg.json | 12 +++ .../deployWithNewResourceGroup/readme.md | 54 +++++++----- .../template-AzureBot-new-rg.json | 40 ++++++++- .../template-BotApp-new-rg.json | 76 ++++++++++++++++- 30 files changed, 1115 insertions(+), 164 deletions(-) diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json index cc1800c0d..92b2e84c9 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json @@ -14,8 +14,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json index eea65b7c3..eb36c03fb 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json @@ -15,7 +15,7 @@ "value": "" }, "newAppServicePlanLocation": { - "value": "" + "value": "West US" }, "newAppServicePlanSku": { "value": { @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md index f8f1d1e56..19d77be80 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md @@ -1,28 +1,48 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment group create --resource-group --template-file --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment group create --resource-group --template-file --parameters @` # parameters-for-template-BotApp-with-rg: -**appServiceName**:(required) The Name of the Bot App Service. +- **appServiceName**:(required) The Name of the Bot App Service. + +- (choose an existingAppServicePlan or create a new AppServicePlan) + - **existingAppServicePlanName**: The name of the App Service Plan. + - **existingAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanName**: The name of the App Service Plan. + - **newAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** + +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. + +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. + +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. + +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. + +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -(choose an existingAppServicePlan or create a new AppServicePlan) -**existingAppServicePlanName**: The name of the App Service Plan. -**existingAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanName**: The name of the App Service Plan. -**newAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-with-rg: -**azureBotId**:(required) The globally unique and immutable bot ID. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **azureBotId**:(required) The globally unique and immutable bot ID. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json index f7d08b75d..b2b686dcc 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json @@ -9,8 +9,8 @@ } }, "azureBotSku": { - "defaultValue": "S1", "type": "string", + "defaultValue": "S1", "metadata": { "description": "The pricing tier of the Bot Service Registration. Allowed values are: F0, S1(default)." } @@ -24,15 +24,72 @@ }, "botEndpoint": { "type": "string", + "defaultValue": "", "metadata": { "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" } }, "resources": [ @@ -49,8 +106,11 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json index 9b1c79ae9..23a23b1cc 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json @@ -4,7 +4,6 @@ "parameters": { "appServiceName": { "type": "string", - "defaultValue": "", "metadata": { "description": "The globally unique name of the Web App." } @@ -18,18 +17,21 @@ }, "existingAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } }, "newAppServicePlanName": { "type": "string", + "defaultValue": "", "metadata": { "description": "The name of the new App Service Plan." } }, "newAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -47,6 +49,18 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -59,13 +73,58 @@ "metadata": { "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } } }, "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlanName')), 'createNewAppServicePlan', parameters('existingAppServicePlanName'))]", - "useExistingServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "useExistingServicePlan": "[not(empty(parameters('existingAppServicePlanName')))]", "servicePlanName": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanName'), parameters('newAppServicePlanName'))]", - "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]" + "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -89,13 +148,15 @@ "comments": "Create a Web App using an App Service Plan", "type": "Microsoft.Web/sites", "apiVersion": "2015-08-01", - "name": "[parameters('appServiceName')]", "location": "[variables('servicePlanLocation')]", "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], + "name": "[parameters('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { + "name": "[parameters('appServiceName')]", "enabled": true, "hostNameSslStates": [ { @@ -124,6 +185,10 @@ "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "true" }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" @@ -131,6 +196,10 @@ { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -167,7 +236,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -201,7 +270,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json index f18061813..e51036f85 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json @@ -20,8 +20,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json index f3f07b497..de2dba051 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md index d88b160f1..4c752364b 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md @@ -1,31 +1,45 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment sub create --template-file --location --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment sub create --template-file --location --parameters @` # parameters-for-template-BotApp-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **appServiceName**:(required) The location of the App Service Plan. +- **appServicePlanName**:(required) The name of the App Service Plan. +- **appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. +- **appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appServiceName**:(required) The location of the App Service Plan. -**appServicePlanName**:(required) The name of the App Service Plan. -**appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. -**appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. -**azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json index f79264452..63fbf970d 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json @@ -41,11 +41,47 @@ "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]" } }, "resources": [ @@ -85,8 +121,10 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json index b33b21510..381b57abf 100644 --- a/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json +++ b/generators/app/templates/core/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json @@ -28,6 +28,7 @@ }, "appServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -45,6 +46,25 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -53,16 +73,56 @@ }, "appSecret": { "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types." + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." } } }, "variables": { + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", "appServicePlanName": "[parameters('appServicePlanName')]", "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), parameters('groupLocation'), parameters('appServicePlanLocation'))]", "appServiceName": "[parameters('appServiceName')]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -114,6 +174,7 @@ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { "name": "[variables('appServiceName')]", "hostNameSslStates": [ @@ -136,12 +197,19 @@ "value": "true" }, { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" }, { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -179,7 +247,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -213,7 +281,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json index cc1800c0d..92b2e84c9 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json @@ -14,8 +14,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json index eea65b7c3..eb36c03fb 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json @@ -15,7 +15,7 @@ "value": "" }, "newAppServicePlanLocation": { - "value": "" + "value": "West US" }, "newAppServicePlanSku": { "value": { @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md index f8f1d1e56..19d77be80 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md @@ -1,28 +1,48 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment group create --resource-group --template-file --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment group create --resource-group --template-file --parameters @` # parameters-for-template-BotApp-with-rg: -**appServiceName**:(required) The Name of the Bot App Service. +- **appServiceName**:(required) The Name of the Bot App Service. + +- (choose an existingAppServicePlan or create a new AppServicePlan) + - **existingAppServicePlanName**: The name of the App Service Plan. + - **existingAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanName**: The name of the App Service Plan. + - **newAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** + +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. + +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. + +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. + +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. + +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -(choose an existingAppServicePlan or create a new AppServicePlan) -**existingAppServicePlanName**: The name of the App Service Plan. -**existingAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanName**: The name of the App Service Plan. -**newAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-with-rg: -**azureBotId**:(required) The globally unique and immutable bot ID. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **azureBotId**:(required) The globally unique and immutable bot ID. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json index f7d08b75d..b2b686dcc 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json @@ -9,8 +9,8 @@ } }, "azureBotSku": { - "defaultValue": "S1", "type": "string", + "defaultValue": "S1", "metadata": { "description": "The pricing tier of the Bot Service Registration. Allowed values are: F0, S1(default)." } @@ -24,15 +24,72 @@ }, "botEndpoint": { "type": "string", + "defaultValue": "", "metadata": { "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" } }, "resources": [ @@ -49,8 +106,11 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json index 9b1c79ae9..23a23b1cc 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json @@ -4,7 +4,6 @@ "parameters": { "appServiceName": { "type": "string", - "defaultValue": "", "metadata": { "description": "The globally unique name of the Web App." } @@ -18,18 +17,21 @@ }, "existingAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } }, "newAppServicePlanName": { "type": "string", + "defaultValue": "", "metadata": { "description": "The name of the new App Service Plan." } }, "newAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -47,6 +49,18 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -59,13 +73,58 @@ "metadata": { "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } } }, "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlanName')), 'createNewAppServicePlan', parameters('existingAppServicePlanName'))]", - "useExistingServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "useExistingServicePlan": "[not(empty(parameters('existingAppServicePlanName')))]", "servicePlanName": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanName'), parameters('newAppServicePlanName'))]", - "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]" + "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -89,13 +148,15 @@ "comments": "Create a Web App using an App Service Plan", "type": "Microsoft.Web/sites", "apiVersion": "2015-08-01", - "name": "[parameters('appServiceName')]", "location": "[variables('servicePlanLocation')]", "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], + "name": "[parameters('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { + "name": "[parameters('appServiceName')]", "enabled": true, "hostNameSslStates": [ { @@ -124,6 +185,10 @@ "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "true" }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" @@ -131,6 +196,10 @@ { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -167,7 +236,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -201,7 +270,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json index f18061813..e51036f85 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json @@ -20,8 +20,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json index f3f07b497..de2dba051 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md index d88b160f1..4c752364b 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md @@ -1,31 +1,45 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment sub create --template-file --location --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment sub create --template-file --location --parameters @` # parameters-for-template-BotApp-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **appServiceName**:(required) The location of the App Service Plan. +- **appServicePlanName**:(required) The name of the App Service Plan. +- **appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. +- **appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appServiceName**:(required) The location of the App Service Plan. -**appServicePlanName**:(required) The name of the App Service Plan. -**appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. -**appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. -**azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json index f79264452..63fbf970d 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json @@ -41,11 +41,47 @@ "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]" } }, "resources": [ @@ -85,8 +121,10 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json index b33b21510..381b57abf 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json @@ -28,6 +28,7 @@ }, "appServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -45,6 +46,25 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -53,16 +73,56 @@ }, "appSecret": { "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types." + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." } } }, "variables": { + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", "appServicePlanName": "[parameters('appServicePlanName')]", "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), parameters('groupLocation'), parameters('appServicePlanLocation'))]", "appServiceName": "[parameters('appServiceName')]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -114,6 +174,7 @@ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { "name": "[variables('appServiceName')]", "hostNameSslStates": [ @@ -136,12 +197,19 @@ "value": "true" }, { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" }, { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -179,7 +247,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -213,7 +281,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json index cc1800c0d..92b2e84c9 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json @@ -14,8 +14,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json index eea65b7c3..eb36c03fb 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json @@ -15,7 +15,7 @@ "value": "" }, "newAppServicePlanLocation": { - "value": "" + "value": "West US" }, "newAppServicePlanSku": { "value": { @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md index f8f1d1e56..19d77be80 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/readme.md @@ -1,28 +1,48 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment group create --resource-group --template-file --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment group create --resource-group --template-file --parameters @` # parameters-for-template-BotApp-with-rg: -**appServiceName**:(required) The Name of the Bot App Service. +- **appServiceName**:(required) The Name of the Bot App Service. + +- (choose an existingAppServicePlan or create a new AppServicePlan) + - **existingAppServicePlanName**: The name of the App Service Plan. + - **existingAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanName**: The name of the App Service Plan. + - **newAppServicePlanLocation**: The location of the App Service Plan. + - **newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** + +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. + +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. + +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. + +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. + +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -(choose an existingAppServicePlan or create a new AppServicePlan) -**existingAppServicePlanName**: The name of the App Service Plan. -**existingAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanName**: The name of the App Service Plan. -**newAppServicePlanLocation**: The location of the App Service Plan. -**newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-with-rg: -**azureBotId**:(required) The globally unique and immutable bot ID. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **azureBotId**:(required) The globally unique and immutable bot ID. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. + +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json index f7d08b75d..b2b686dcc 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-AzureBot-with-rg.json @@ -9,8 +9,8 @@ } }, "azureBotSku": { - "defaultValue": "S1", "type": "string", + "defaultValue": "S1", "metadata": { "description": "The pricing tier of the Bot Service Registration. Allowed values are: F0, S1(default)." } @@ -24,15 +24,72 @@ }, "botEndpoint": { "type": "string", + "defaultValue": "", "metadata": { "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "[variables('msiResourceId')]" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" } }, "resources": [ @@ -49,8 +106,11 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppMSIResourceId": "[variables('appType').msiResourceId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json index 9b1c79ae9..979ec221b 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json @@ -4,7 +4,6 @@ "parameters": { "appServiceName": { "type": "string", - "defaultValue": "", "metadata": { "description": "The globally unique name of the Web App." } @@ -18,18 +17,21 @@ }, "existingAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } }, "newAppServicePlanName": { "type": "string", + "defaultValue": "", "metadata": { "description": "The name of the new App Service Plan." } }, "newAppServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -47,6 +49,18 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -59,13 +73,58 @@ "metadata": { "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } } }, "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlanName')), 'createNewAppServicePlan', parameters('existingAppServicePlanName'))]", - "useExistingServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "useExistingServicePlan": "[not(empty(parameters('existingAppServicePlanName')))]", "servicePlanName": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanName'), parameters('newAppServicePlanName'))]", - "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]" + "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -89,13 +148,15 @@ "comments": "Create a Web App using an App Service Plan", "type": "Microsoft.Web/sites", "apiVersion": "2015-08-01", - "name": "[parameters('appServiceName')]", "location": "[variables('servicePlanLocation')]", "kind": "app,linux", "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], + "name": "[parameters('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { + "name": "[parameters('appServiceName')]", "enabled": true, "hostNameSslStates": [ { @@ -124,6 +185,10 @@ "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "true" }, + { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" @@ -131,6 +196,10 @@ { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -167,7 +236,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -201,7 +270,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json index f18061813..e51036f85 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json @@ -20,8 +20,20 @@ "botEndpoint": { "value": "" }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" + }, + "tenantId": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json index f3f07b497..de2dba051 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json @@ -26,11 +26,23 @@ "capacity": 1 } }, + "appType": { + "value": "MultiTenant" + }, "appId": { "value": "" }, "appSecret": { "value": "" + }, + "tenantId": { + "value": "" + }, + "UMSIName": { + "value": "" + }, + "UMSIResourceGroupName": { + "value": "" } } } \ No newline at end of file diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md index d88b160f1..4c752364b 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/readme.md @@ -1,31 +1,45 @@ -Need deploy BotAppService before AzureBot ---- -az login -az deployment sub create --template-file --location --parameters @ ---- +# Usage +BotApp must be deployed prior to AzureBot. + +### Command line: +`az login`
+`az deployment sub create --template-file --location --parameters @` # parameters-for-template-BotApp-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **appServiceName**:(required) The location of the App Service Plan. +- **appServicePlanName**:(required) The name of the App Service Plan. +- **appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. +- **appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. -**appServiceName**:(required) The location of the App Service Plan. -**appServicePlanName**:(required) The name of the App Service Plan. -**appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. -**appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . + +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -**appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. # parameters-for-template-AzureBot-new-rg: -**groupName**:(required) Specifies the name of the new Resource Group. -**groupLocation**:(required) Specifies the location of the new Resource Group. +- **groupName**:(required) Specifies the name of the new Resource Group. +- **groupLocation**:(required) Specifies the location of the new Resource Group. + +- **azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. +- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. +- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. +- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. -**azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. -**azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -**azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -**botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. +- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** +- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. +- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. +- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. +- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . -**appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. \ No newline at end of file +MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json index f79264452..63fbf970d 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-AzureBot-new-rg.json @@ -41,11 +41,47 @@ "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." } }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + } + }, + "variables": { + "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "msiResourceId": "" + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "msiResourceId": "" + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]" } }, "resources": [ @@ -85,8 +121,10 @@ "name": "[parameters('azureBotId')]", "displayName": "[parameters('azureBotId')]", "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[parameters('botEndpoint')]", + "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('appId')]", + "msaAppTenantId": "[variables('appType').tenantId]", + "msaAppType": "[parameters('appType')]", "luisAppIds": [], "schemaTransformationVersion": "1.3", "isCmekEnabled": false, diff --git a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json index b33b21510..381b57abf 100644 --- a/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json +++ b/generators/app/templates/empty/{{cookiecutter.bot_name}}/deploymentTemplates/deployWithNewResourceGroup/template-BotApp-new-rg.json @@ -28,6 +28,7 @@ }, "appServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -45,6 +46,25 @@ "description": "The SKU of the App Service Plan. Defaults to Standard values." } }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." + } + }, + "appType": { + "type": "string", + "defaultValue": "MultiTenant", + "allowedValues": [ + "MultiTenant", + "SingleTenant", + "UserAssignedMSI" + ], + "metadata": { + "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." + } + }, "appId": { "type": "string", "metadata": { @@ -53,16 +73,56 @@ }, "appSecret": { "type": "string", + "defaultValue": "", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." + } + }, + "UMSIName": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types." + "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." + } + }, + "UMSIResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." } } }, "variables": { + "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", "appServicePlanName": "[parameters('appServicePlanName')]", "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), parameters('groupLocation'), parameters('appServicePlanLocation'))]", "appServiceName": "[parameters('appServiceName')]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", + "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", + "appTypeDef": { + "MultiTenant": { + "tenantId": "", + "identity": { "type": "None" } + }, + "SingleTenant": { + "tenantId": "[variables('tenantId')]", + "identity": { "type": "None" } + }, + "UserAssignedMSI": { + "tenantId": "[variables('tenantId')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[variables('msiResourceId')]": {} + } + } + } + }, + "appType": { + "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", + "identity": "[variables('appTypeDef')[parameters('appType')].identity]" + } }, "resources": [ { @@ -114,6 +174,7 @@ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('appServiceName')]", + "identity": "[variables('appType').identity]", "properties": { "name": "[variables('appServiceName')]", "hostNameSslStates": [ @@ -136,12 +197,19 @@ "value": "true" }, { + "name": "MicrosoftAppType", + "value": "[parameters('appType')]" + }, { "name": "MicrosoftAppId", "value": "[parameters('appId')]" }, { "name": "MicrosoftAppPassword", "value": "[parameters('appSecret')]" + }, + { + "name": "MicrosoftAppTenantId", + "value": "[variables('appType').tenantId]" } ], "cors": { @@ -179,7 +247,7 @@ "phpVersion": "", "pythonVersion": "", "nodeVersion": "", - "linuxFxVersion": "PYTHON|3.7", + "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, "remoteDebuggingVersion": "VS2017", @@ -213,7 +281,7 @@ "autoHealEnabled": false, "vnetName": "", "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", + "ftpsState": "Disabled", "reservedInstanceCount": 0 } } From 394d3d6b3a4946fe32a875ef1a0fdc230310edc5 Mon Sep 17 00:00:00 2001 From: gandiddi <164224646+gandiddi@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:41:42 +0530 Subject: [PATCH 091/102] Bumped pytest (#2189) Co-authored-by: tracyboehrer --- .../tests/teams/test_teams_activity_handler.py | 4 ++++ .../tests/choices/test_choice_recognizers.py | 7 +++---- libraries/botbuilder-testing/setup.py | 2 +- libraries/botframework-connector/tests/requirements.txt | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 22550764e..257dc75f9 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -5,6 +5,10 @@ from typing import List import aiounittest +import sys +import os + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from botbuilder.core import BotAdapter, TurnContext from botbuilder.core.teams import TeamsActivityHandler from botbuilder.schema import ( diff --git a/libraries/botbuilder-dialogs/tests/choices/test_choice_recognizers.py b/libraries/botbuilder-dialogs/tests/choices/test_choice_recognizers.py index c37243fd1..ac202d044 100644 --- a/libraries/botbuilder-dialogs/tests/choices/test_choice_recognizers.py +++ b/libraries/botbuilder-dialogs/tests/choices/test_choice_recognizers.py @@ -57,10 +57,9 @@ def assert_choice(result, value, index, score, synonym=None): resolution.score == score ), f"Invalid resolution.score of '{resolution.score}' for '{value}' choice." if synonym: - assert ( # pylint: disable=assert-on-tuple - resolution.synonym == synonym, - f"Invalid resolution.synonym of '{resolution.synonym}' for '{value}' choice.", - ) + assert ( + resolution.synonym == synonym + ), f"Invalid resolution.synonym of '{resolution.synonym}' for '{value}' choice." _color_choices: List[str] = ["red", "green", "blue"] diff --git a/libraries/botbuilder-testing/setup.py b/libraries/botbuilder-testing/setup.py index 9fed4e3ac..9ee855a41 100644 --- a/libraries/botbuilder-testing/setup.py +++ b/libraries/botbuilder-testing/setup.py @@ -9,7 +9,7 @@ "botbuilder-core==4.17.0", "botbuilder-dialogs==4.17.0", "botbuilder-azure==4.17.0", - "pytest~=7.3.1", + "pytest~=8.3.3", ] TESTS_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botframework-connector/tests/requirements.txt b/libraries/botframework-connector/tests/requirements.txt index 5f0d9558d..6facda892 100644 --- a/libraries/botframework-connector/tests/requirements.txt +++ b/libraries/botframework-connector/tests/requirements.txt @@ -1,6 +1,6 @@ -pytest-cov>=2.6.0 -pytest~=7.3.1 +pytest-cov>=5.0.0 +pytest~=8.3.3 pyyaml==6.0.1 -pytest-asyncio==0.23.8 +pytest-asyncio==0.24.0 ddt==1.2.1 setuptools==72.1.0 \ No newline at end of file From a7f5d91398bf502128645ec5d698024c5aaeda3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:23:34 -0600 Subject: [PATCH 092/102] Bump: aiohttp from 3.10.5 to 3.10.11 (#2190) * Bump aiohttp Bumps the pip group with 1 update in the /libraries/botbuilder-integration-aiohttp directory: [aiohttp](https://github.com/aio-libs/aiohttp). Updates `aiohttp` from 3.10.5 to 3.10.11 - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.5...v3.10.11) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] * Bump: aiohttp to 3.10.11 in SDK packages --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ganapathi Diddi Co-authored-by: tracyboehrer --- libraries/botbuilder-adapters-slack/requirements.txt | 2 +- libraries/botbuilder-ai/setup.py | 2 +- libraries/botbuilder-integration-aiohttp/requirements.txt | 2 +- libraries/botbuilder-integration-aiohttp/setup.py | 2 +- .../botbuilder-integration-applicationinsights-aiohttp/setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 98af56627..8dd02c6be 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.10.5 +aiohttp==3.10.11 pyslack botbuilder-core==4.17.0 slackclient diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index e842fdc34..707369967 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.17.0", "botbuilder-core==4.17.0", - "aiohttp==3.10.5", + "aiohttp==3.10.11", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index de0f13750..4344965ca 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.17.0 botbuilder-schema==4.17.0 -aiohttp==3.10.5 +aiohttp==3.10.11 diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index 1fe5d5ccc..635e26fc1 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.17.0", "botbuilder-core==4.17.0", "yarl>=1.8.1", - "aiohttp==3.10.5", + "aiohttp==3.10.11", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index 30c45b2f7..3ef0ca426 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.10.5", + "aiohttp==3.10.11", "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", "botbuilder-core==4.17.0", From ef5ecd36f76529f30727929128330fd790dc5625 Mon Sep 17 00:00:00 2001 From: Kostya <53430368+kostyaplis@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:54:12 +0800 Subject: [PATCH 093/102] Port auth error handling from microsoft_app_credentials to certificate_app_credentials (#2196) Co-authored-by: Konstantin Plis --- .../connector/auth/certificate_app_credentials.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py index 89dbe882d..31e845eb6 100644 --- a/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/certificate_app_credentials.py @@ -67,7 +67,17 @@ def get_access_token(self, force_refresh: bool = False) -> str: if not auth_token: # No suitable token exists in cache. Let's get a new one from AAD. auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) - return auth_token["access_token"] + if "access_token" in auth_token: + return auth_token["access_token"] + error = auth_token["error"] if "error" in auth_token else "Unknown error" + error_description = ( + auth_token["error_description"] + if "error_description" in auth_token + else "Unknown error description" + ) + raise PermissionError( + f"Failed to get access token with error: {error}, error_description: {error_description}" + ) def __get_msal_app(self): if not self.app: From bd5662abb1297ebd457ace2918c26e5d611271e9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 11 Feb 2025 12:38:07 -0600 Subject: [PATCH 094/102] Updated MSAL package (#2203) Co-authored-by: Tracy Boehrer --- libraries/botframework-connector/requirements.txt | 2 +- libraries/botframework-connector/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botframework-connector/requirements.txt b/libraries/botframework-connector/requirements.txt index 5a6d8d4e9..515030672 100644 --- a/libraries/botframework-connector/requirements.txt +++ b/libraries/botframework-connector/requirements.txt @@ -3,4 +3,4 @@ botbuilder-schema==4.17.0 requests==2.32.0 PyJWT==2.4.0 cryptography==43.0.1 -msal>=1.29.0 +msal>=1.31.1 diff --git a/libraries/botframework-connector/setup.py b/libraries/botframework-connector/setup.py index 8a99b3e19..1bfc05d49 100644 --- a/libraries/botframework-connector/setup.py +++ b/libraries/botframework-connector/setup.py @@ -11,7 +11,7 @@ # "requests>=2.23.0,<2.26", "PyJWT>=2.4.0", "botbuilder-schema==4.17.0", - "msal>=1.29.0", + "msal>=1.31.1", ] root = os.path.abspath(os.path.dirname(__file__)) From e2015ebea8bbe000cb19f9c2c25c20b9575085e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Su=C3=A1rez?= Date: Fri, 21 Mar 2025 12:24:45 -0600 Subject: [PATCH 095/102] MentionRemoved fix (#2216) --- .../botbuilder/core/turn_context.py | 6 ++- .../tests/test_turn_context.py | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/turn_context.py b/libraries/botbuilder-core/botbuilder/core/turn_context.py index 852fd1f31..72e25726c 100644 --- a/libraries/botbuilder-core/botbuilder/core/turn_context.py +++ b/libraries/botbuilder-core/botbuilder/core/turn_context.py @@ -396,9 +396,13 @@ def remove_mention_text(activity: Activity, identifier: str) -> str: mentions = TurnContext.get_mentions(activity) for mention in mentions: if mention.additional_properties["mentioned"]["id"] == identifier: + replace_text = ( + mention.additional_properties.get("text") + or mention.additional_properties.get("mentioned")["name"] + ) mention_name_match = re.match( r"(.*?)<\/at>", - escape(mention.additional_properties["text"]), + escape(replace_text), re.IGNORECASE, ) if mention_name_match: diff --git a/libraries/botbuilder-core/tests/test_turn_context.py b/libraries/botbuilder-core/tests/test_turn_context.py index 473580ef0..7247caab9 100644 --- a/libraries/botbuilder-core/tests/test_turn_context.py +++ b/libraries/botbuilder-core/tests/test_turn_context.py @@ -350,6 +350,48 @@ def test_should_remove_at_mention_with_regex_characters(self): assert text == " test activity" assert activity.text == " test activity" + def test_should_remove_custom_mention_from_activity(self): + activity = Activity( + text="Hallo", + text_format="plain", + type="message", + timestamp="2025-03-11T14:16:47.0093935Z", + id="1741702606984", + channel_id="msteams", + service_url="https://smba.trafficmanager.net/emea/REDACTED/", + from_property=ChannelAccount( + id="29:1J-K4xVh-sLpdwQ-R5GkOZ_TB0W3ec_37p710aH8qe8bITA0zxdgIGc9l-MdDdkdE_jasSfNOeWXyyL1nsrHtBQ", + name="", + aad_object_id="REDACTED", + ), + conversation=ConversationAccount( + is_group=True, + conversation_type="groupChat", + tenant_id="REDACTED", + id="19:Ql86tXNM2lTBXNKJdqKdwIF9ltGZwpvluLvnJdA0tmg1@thread.v2", + ), + recipient=ChannelAccount( + id="28:c5d5fb56-a1a4-4467-a7a3-1b37905498a0", name="Azure AI Agent" + ), + entities=[ + Entity().deserialize( + Mention( + type="mention", + mentioned=ChannelAccount( + id="28:c5d5fb56-a1a4-4467-a7a3-1b37905498a0", + name="Custom Agent", + ), + ).serialize() + ) + ], + channel_data={"tenant": {"id": "REDACTED"}, "productContext": "COPILOT"}, + ) + + text = TurnContext.remove_mention_text(activity, activity.recipient.id) + + assert text == "Hallo" + assert activity.text == "Hallo" + async def test_should_send_a_trace_activity(self): context = TurnContext(SimpleAdapter(), ACTIVITY) called = False From ec22a895ddea708d4ebcb6497f9521f9de58f4ba Mon Sep 17 00:00:00 2001 From: Alex Recuenco <26118630+alexrecuenco@users.noreply.github.com> Date: Tue, 25 Mar 2025 18:23:32 +0100 Subject: [PATCH 096/102] fix: Method name is emit_event, not emit (#2215) --- libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py index 43cfe3052..f07a8afa5 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog.py @@ -125,7 +125,7 @@ async def on_dialog_event( # Bubble as needed if (not handled) and dialog_event.bubble and dialog_context.parent: - handled = await dialog_context.parent.emit( + handled = await dialog_context.parent.emit_event( dialog_event.name, dialog_event.value, True, False ) From 13ee2f25ec3b7470fc4f570d7624b59487a98d38 Mon Sep 17 00:00:00 2001 From: Alex Recuenco <26118630+alexrecuenco@users.noreply.github.com> Date: Tue, 25 Mar 2025 18:24:05 +0100 Subject: [PATCH 097/102] Typo on link (#2217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit contributing goes to a 404 — file not found, https://github.com/microsoft/botbuilder-python/blob/main/contributing.md The correct link is https://github.com/microsoft/botbuilder-python/blob/main/Contributing.md Co-authored-by: tracyboehrer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92a7c1332..feace0e06 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ We use the [@msbotframework](https://twitter.com/msbotframework) account on twit The [Gitter Channel](https://gitter.im/Microsoft/BotBuilder) provides a place where the Community can get together and collaborate. ## Contributing and our code of conduct -We welcome contributions and suggestions. Please see our [contributing guidelines](./contributing.md) for more information. +We welcome contributions and suggestions. Please see our [contributing guidelines](./Contributing.md) for more information. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). From ac28ca3d54a124cfc2c38765f64b9008e76801ab Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 28 May 2025 12:17:53 -0500 Subject: [PATCH 098/102] Teams SSO and OAuth fixes (#2226) * Fixed Teams SSO & OAuth * Formatting --------- Co-authored-by: Tracy Boehrer --- .../requirements.txt | 2 +- libraries/botbuilder-ai/setup.py | 2 +- .../botbuilder/core/__init__.py | 2 ++ .../botbuilder/core/activity_handler.py | 11 +++++-- .../core/teams/teams_activity_handler.py | 11 ++----- .../teams_sso_token_exchange_middleware.py | 19 ++++++++++-- .../dialogs/prompts/oauth_prompt.py | 8 +++++ .../integration/aiohttp/cloud_adapter.py | 6 +++- .../requirements.txt | 2 +- .../botbuilder-integration-aiohttp/setup.py | 2 +- .../setup.py | 2 +- .../botbuilder/schema/_models_py3.py | 3 ++ .../connector/token_api/models/__init__.py | 3 ++ .../connector/token_api/models/_models.py | 24 ++++++++++++++ .../connector/token_api/models/_models_py3.py | 31 ++++++++++++++++++- .../skills-buffered/child/requirements.txt | 2 +- .../skills-buffered/parent/requirements.txt | 2 +- 17 files changed, 110 insertions(+), 22 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/requirements.txt b/libraries/botbuilder-adapters-slack/requirements.txt index 8dd02c6be..50f1af767 100644 --- a/libraries/botbuilder-adapters-slack/requirements.txt +++ b/libraries/botbuilder-adapters-slack/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.10.11 +aiohttp pyslack botbuilder-core==4.17.0 slackclient diff --git a/libraries/botbuilder-ai/setup.py b/libraries/botbuilder-ai/setup.py index 707369967..10bc3ee5c 100644 --- a/libraries/botbuilder-ai/setup.py +++ b/libraries/botbuilder-ai/setup.py @@ -8,7 +8,7 @@ "azure-cognitiveservices-language-luis==0.2.0", "botbuilder-schema==4.17.0", "botbuilder-core==4.17.0", - "aiohttp==3.10.11", + "aiohttp>=3.10,<4.0", ] TESTS_REQUIRES = ["aiounittest>=1.1.0"] diff --git a/libraries/botbuilder-core/botbuilder/core/__init__.py b/libraries/botbuilder-core/botbuilder/core/__init__.py index c5c038353..0769d9100 100644 --- a/libraries/botbuilder-core/botbuilder/core/__init__.py +++ b/libraries/botbuilder-core/botbuilder/core/__init__.py @@ -48,6 +48,7 @@ from .user_state import UserState from .register_class_middleware import RegisterClassMiddleware from .adapter_extensions import AdapterExtensions +from .serializer_helper import serializer_helper __all__ = [ "ActivityHandler", @@ -100,5 +101,6 @@ "TurnContext", "UserState", "UserTokenProvider", + "serializer_helper", "__version__", ] diff --git a/libraries/botbuilder-core/botbuilder/core/activity_handler.py b/libraries/botbuilder-core/botbuilder/core/activity_handler.py index 51aefb245..4dbf04f0b 100644 --- a/libraries/botbuilder-core/botbuilder/core/activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/activity_handler.py @@ -478,12 +478,19 @@ async def on_invoke_activity( # pylint: disable=unused-argument if ( turn_context.activity.name == SignInConstants.verify_state_operation_name - or turn_context.activity.name - == SignInConstants.token_exchange_operation_name ): await self.on_sign_in_invoke(turn_context) return self._create_invoke_response() + # This is for back-compat with previous versions of Python SDK. This method does not + # exist in the C# SDK, and is not used in the Python SDK. + if ( + turn_context.activity.name + == SignInConstants.token_exchange_operation_name + ): + await self.on_teams_signin_token_exchange(turn_context) + return self._create_invoke_response() + if turn_context.activity.name == "adaptiveCard/action": invoke_value = self._get_adaptive_card_invoke_value( turn_context.activity diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 9344e343e..2cc2e6fb6 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -56,13 +56,6 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: ): return await self.on_teams_card_action_invoke(turn_context) - if ( - turn_context.activity.name - == SignInConstants.token_exchange_operation_name - ): - await self.on_teams_signin_token_exchange(turn_context) - return self._create_invoke_response() - if turn_context.activity.name == "fileConsent/invoke": return await self.on_teams_file_consent( turn_context, @@ -250,7 +243,9 @@ async def on_teams_signin_verify_state(self, turn_context: TurnContext): raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) async def on_teams_signin_token_exchange(self, turn_context: TurnContext): - raise _InvokeResponseException(status_code=HTTPStatus.NOT_IMPLEMENTED) + # This is for back-compat with previous versions of Python SDK. This method does not + # exist in the C# SDK, and is not used in the Python SDK. + return await self.on_teams_signin_verify_state(turn_context) async def on_teams_file_consent( self, diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_sso_token_exchange_middleware.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_sso_token_exchange_middleware.py index 1dec1210a..5a6fa5de6 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_sso_token_exchange_middleware.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_sso_token_exchange_middleware.py @@ -26,6 +26,7 @@ StoreItem, TurnContext, ) +from botframework.connector.auth.user_token_client import UserTokenClient class _TokenStoreItem(StoreItem): @@ -147,17 +148,29 @@ 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"]: + for prop in ["id", "connectionName", "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"], + connection_name=aux_dict["connectionName"], token=aux_dict["token"], properties=aux_dict["properties"], ) try: adapter = turn_context.adapter - if isinstance(turn_context.adapter, ExtendedUserTokenProvider): + + user_token_client: UserTokenClient = turn_context.turn_state.get( + UserTokenClient.__name__, None + ) + if user_token_client: + # If the adapter has UserTokenClient, use it to exchange the token. + token_exchange_response = await user_token_client.exchange_token( + turn_context.activity.from_property.id, + token_exchange_request.connection_name, + turn_context.activity.channel_id, + TokenExchangeRequest(token=token_exchange_request.token), + ) + elif isinstance(turn_context.adapter, ExtendedUserTokenProvider): token_exchange_response = await adapter.exchange_token( turn_context, self._oauth_connection_name, diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py index d31a0b56a..270d4f324 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py @@ -341,6 +341,13 @@ async def _send_oauth_card( if sign_in_resource.token_exchange_resource else None ) + + json_token_ex_post = ( + sign_in_resource.token_post_resource.as_dict() + if sign_in_resource.token_post_resource + else None + ) + prompt.attachments.append( CardFactory.oauth_card( OAuthCard( @@ -355,6 +362,7 @@ async def _send_oauth_card( ) ], token_exchange_resource=json_token_ex_resource, + token_post_resource=json_token_ex_post, ) ) ) diff --git a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py index 0f9131871..576c5125c 100644 --- a/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py +++ b/libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/cloud_adapter.py @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import json + from typing import Awaitable, Callable, Optional from aiohttp.web import ( @@ -17,6 +19,7 @@ Bot, CloudAdapterBase, InvokeResponse, + serializer_helper, TurnContext, ) from botbuilder.core.streaming import ( @@ -102,7 +105,8 @@ async def process( # Write the response, serializing the InvokeResponse if invoke_response: return json_response( - data=invoke_response.body, status=invoke_response.status + data=serializer_helper(invoke_response.body), + status=invoke_response.status, ) return Response(status=201) else: diff --git a/libraries/botbuilder-integration-aiohttp/requirements.txt b/libraries/botbuilder-integration-aiohttp/requirements.txt index 4344965ca..d66ba0327 100644 --- a/libraries/botbuilder-integration-aiohttp/requirements.txt +++ b/libraries/botbuilder-integration-aiohttp/requirements.txt @@ -1,4 +1,4 @@ msrest== 0.7.* botframework-connector==4.17.0 botbuilder-schema==4.17.0 -aiohttp==3.10.11 +aiohttp==3.*.* diff --git a/libraries/botbuilder-integration-aiohttp/setup.py b/libraries/botbuilder-integration-aiohttp/setup.py index 635e26fc1..2624c9dc8 100644 --- a/libraries/botbuilder-integration-aiohttp/setup.py +++ b/libraries/botbuilder-integration-aiohttp/setup.py @@ -10,7 +10,7 @@ "botframework-connector==4.17.0", "botbuilder-core==4.17.0", "yarl>=1.8.1", - "aiohttp==3.10.11", + "aiohttp>=3.10,<4.0", ] root = os.path.abspath(os.path.dirname(__file__)) diff --git a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py index 3ef0ca426..78c32e5eb 100644 --- a/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py +++ b/libraries/botbuilder-integration-applicationinsights-aiohttp/setup.py @@ -6,7 +6,7 @@ REQUIRES = [ "applicationinsights>=0.11.9", - "aiohttp==3.10.11", + "aiohttp>=3.10,<4.0", "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", "botbuilder-core==4.17.0", diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 1b6a631c6..e7dd1f789 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -1909,6 +1909,7 @@ class OAuthCard(Model): "connection_name": {"key": "connectionName", "type": "str"}, "buttons": {"key": "buttons", "type": "[CardAction]"}, "token_exchange_resource": {"key": "tokenExchangeResource", "type": "object"}, + "token_post_resource": {"key": "tokenPostResource", "type": "object"}, } def __init__( @@ -1918,6 +1919,7 @@ def __init__( connection_name: str = None, buttons=None, token_exchange_resource=None, + token_post_resource=None, **kwargs ) -> None: super(OAuthCard, self).__init__(**kwargs) @@ -1925,6 +1927,7 @@ def __init__( self.connection_name = connection_name self.buttons = buttons self.token_exchange_resource = token_exchange_resource + self.token_post_resource = token_post_resource class PagedMembersResult(Model): diff --git a/libraries/botframework-connector/botframework/connector/token_api/models/__init__.py b/libraries/botframework-connector/botframework/connector/token_api/models/__init__.py index f4593e21a..0f1f158da 100644 --- a/libraries/botframework-connector/botframework/connector/token_api/models/__init__.py +++ b/libraries/botframework-connector/botframework/connector/token_api/models/__init__.py @@ -13,6 +13,7 @@ from ._models_py3 import SignInUrlResponse from ._models_py3 import TokenExchangeRequest from ._models_py3 import TokenExchangeResource + from ._models_py3 import TokenPostResource from ._models_py3 import TokenResponse from ._models_py3 import TokenStatus except (SyntaxError, ImportError): @@ -23,6 +24,7 @@ from ._models import SignInUrlResponse from ._models import TokenExchangeRequest from ._models import TokenExchangeResource + from ._models import TokenPostResource from ._models import TokenResponse from ._models import TokenStatus @@ -35,6 +37,7 @@ "SignInUrlResponse", "TokenExchangeRequest", "TokenExchangeResource", + "TokenPostResource", "TokenResponse", "TokenStatus", ] diff --git a/libraries/botframework-connector/botframework/connector/token_api/models/_models.py b/libraries/botframework-connector/botframework/connector/token_api/models/_models.py index 5f69104cd..8b526324a 100644 --- a/libraries/botframework-connector/botframework/connector/token_api/models/_models.py +++ b/libraries/botframework-connector/botframework/connector/token_api/models/_models.py @@ -104,6 +104,9 @@ class SignInUrlResponse(Model): :param token_exchange_resource: :type token_exchange_resource: ~botframework.tokenapi.models.TokenExchangeResource + :param token_post_resource: + :type token_post_resource: + ~botframework.tokenapi.models.TokenPostResource """ _attribute_map = { @@ -112,12 +115,17 @@ class SignInUrlResponse(Model): "key": "tokenExchangeResource", "type": "TokenExchangeResource", }, + "token_post_resource": { + "key": "tokenPostResource", + "type": "TokenPostResource", + }, } def __init__(self, **kwargs): super(SignInUrlResponse, self).__init__(**kwargs) self.sign_in_link = kwargs.get("sign_in_link", None) self.token_exchange_resource = kwargs.get("token_exchange_resource", None) + self.token_exchange_resource = kwargs.get("token_post_resource", None) class TokenExchangeRequest(Model): @@ -164,6 +172,22 @@ def __init__(self, **kwargs): self.provider_id = kwargs.get("provider_id", None) +class TokenPostResource(Model): + """TokenPostResource. + + :param sas_url: + :type id: str + """ + + _attribute_map = { + "sas_url": {"key": "sasUrl", "type": "str"}, + } + + def __init__(self, **kwargs): + super(TokenPostResource, self).__init__(**kwargs) + self.sas_url = kwargs.get("sas_url", None) + + class TokenResponse(Model): """TokenResponse. diff --git a/libraries/botframework-connector/botframework/connector/token_api/models/_models_py3.py b/libraries/botframework-connector/botframework/connector/token_api/models/_models_py3.py index 60ab62c92..512e85356 100644 --- a/libraries/botframework-connector/botframework/connector/token_api/models/_models_py3.py +++ b/libraries/botframework-connector/botframework/connector/token_api/models/_models_py3.py @@ -106,6 +106,9 @@ class SignInUrlResponse(Model): :param token_exchange_resource: :type token_exchange_resource: ~botframework.tokenapi.models.TokenExchangeResource + :param token_post_resource: + :type token_post_resource: + ~botframework.tokenapi.models.TokenPostResource """ _attribute_map = { @@ -114,14 +117,24 @@ class SignInUrlResponse(Model): "key": "tokenExchangeResource", "type": "TokenExchangeResource", }, + "token_post_resource": { + "key": "tokenPostResource", + "type": "TokenPostResource", + }, } def __init__( - self, *, sign_in_link: str = None, token_exchange_resource=None, **kwargs + self, + *, + sign_in_link: str = None, + token_exchange_resource=None, + token_post_resource=None, + **kwargs ) -> None: super(SignInUrlResponse, self).__init__(**kwargs) self.sign_in_link = sign_in_link self.token_exchange_resource = token_exchange_resource + self.token_post_resource = token_post_resource class TokenExchangeRequest(Model): @@ -170,6 +183,22 @@ def __init__( self.provider_id = provider_id +class TokenPostResource(Model): + """TokenPostResource. + + :param sas_url: + :type id: str + """ + + _attribute_map = { + "sas_url": {"key": "sasUrl", "type": "str"}, + } + + def __init__(self, *, sas_url: str = None, **kwargs) -> None: + super(TokenPostResource, self).__init__(**kwargs) + self.sas_url = sas_url + + class TokenResponse(Model): """TokenResponse. diff --git a/tests/skills/skills-buffered/child/requirements.txt b/tests/skills/skills-buffered/child/requirements.txt index 20f8f8fe5..9e79c8115 100644 --- a/tests/skills/skills-buffered/child/requirements.txt +++ b/tests/skills/skills-buffered/child/requirements.txt @@ -1,2 +1,2 @@ botbuilder-core>=4.7.1 -aiohttp +aiohttp==3.*.* diff --git a/tests/skills/skills-buffered/parent/requirements.txt b/tests/skills/skills-buffered/parent/requirements.txt index 20f8f8fe5..9e79c8115 100644 --- a/tests/skills/skills-buffered/parent/requirements.txt +++ b/tests/skills/skills-buffered/parent/requirements.txt @@ -1,2 +1,2 @@ botbuilder-core>=4.7.1 -aiohttp +aiohttp==3.*.* From e535fac0c3cd1d0a33395c678d3a38a29cd6a7a4 Mon Sep 17 00:00:00 2001 From: Mark Jan van Kampen Date: Tue, 23 Dec 2025 20:08:24 +0100 Subject: [PATCH 099/102] chore: relaxes json pickle version constraint (#2240) --- libraries/botbuilder-azure/setup.py | 2 +- libraries/botbuilder-core/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder-azure/setup.py b/libraries/botbuilder-azure/setup.py index 9c40b3ab5..7ff214d2e 100644 --- a/libraries/botbuilder-azure/setup.py +++ b/libraries/botbuilder-azure/setup.py @@ -10,7 +10,7 @@ "azure-storage-queue==12.4.0", "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", - "jsonpickle>=1.2,<1.5", + "jsonpickle>=1.2,<4", ] TEST_REQUIRES = ["aiounittest==1.3.0"] diff --git a/libraries/botbuilder-core/setup.py b/libraries/botbuilder-core/setup.py index a4a0ed1af..24267bfb6 100644 --- a/libraries/botbuilder-core/setup.py +++ b/libraries/botbuilder-core/setup.py @@ -9,7 +9,7 @@ "botbuilder-schema==4.17.0", "botframework-connector==4.17.0", "botframework-streaming==4.17.0", - "jsonpickle>=1.2,<1.5", + "jsonpickle>=1.2,<4", ] root = os.path.abspath(os.path.dirname(__file__)) From df350e43f411e8b471f4bffc0df246d35c9440a6 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 23 Dec 2025 13:53:28 -0600 Subject: [PATCH 100/102] README update (#2245) Co-authored-by: Tracy Boehrer --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index feace0e06..4e90050c0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # ![Bot Framework SDK v4 Python](./doc/media/FrameWorkPython.png) +# ARCHIVE NOTICE: + +> We are in the process of archiving the Bot Framework Python SDK repository on GitHub. This means that this project will no longer be updated or maintained. Customers using this tool will not be disrupted. However, the tool will no longer be supported through +> service tickets in the Azure portal and will not receive product updates. + +> To build agents with your choice of AI services, orchestration, and knowledge, consider using the [Microsoft 365 Agents SDK](https://github.com/microsoft/agents). The Agents SDK is GA and has support for C#, JavaScript or Python. You can learn more about the Agents SDK at aka.ms/agents. If you're looking for a SaaS-based agent platform, consider Microsoft Copilot Studio. If you have an existing bot built with the Bot Framework SDK, you can update your bot to the Agents SDK. You can review the core changes and updates at Bot Framework SDK to Agents SDK migration guidance [here](https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/bf-migration-guidance). Support tickets for the Bot Framework SDK will no longer be serviced as of December 31, 2025. + +> We plan to archive this project no later than end of December of 2025. + This repository contains code for the Python version of the [Microsoft Bot Framework SDK](https://github.com/Microsoft/botframework-sdk), which is part of the Microsoft Bot Framework - a comprehensive framework for building enterprise-grade conversational AI experiences. This SDK enables developers to model conversation and build sophisticated bot applications using Python. SDKs for [JavaScript](https://github.com/Microsoft/botbuilder-js) and [.NET](https://github.com/Microsoft/botbuilder-dotnet) are also available. From 72fa3be2be2c5fdbb88a074ceb2c0f7e1fe21558 Mon Sep 17 00:00:00 2001 From: Hiroshi Yoshioka <40815708+hyoshioka0128@users.noreply.github.com> Date: Tue, 30 Dec 2025 01:53:36 +0900 Subject: [PATCH 101/102] Fix typo in docstring for O365 connector card action (#2244) https://github.com/microsoft/botbuilder-python/blob/main/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py #PingMSFTDocs --- .../botbuilder/core/teams/teams_activity_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 2cc2e6fb6..af45ba5b6 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -314,7 +314,7 @@ async def on_teams_o365_connector_card_action( # pylint: disable=unused-argumen self, turn_context: TurnContext, query: O365ConnectorCardActionQuery ): """ - Invoked when a O365 Connector Card Action activity is received from the connector. + Invoked when an O365 Connector Card Action activity is received from the connector. :param turn_context: A context object for this turn. :param query: The O365 connector card HttpPOST invoke query. From e07ec54ed9a863b69a7cfae5162629383924709d Mon Sep 17 00:00:00 2001 From: warnov Date: Mon, 29 Dec 2025 11:54:12 -0500 Subject: [PATCH 102/102] Update template-BotApp-with-rg.json (#2209) update remoteDebuggingVersion from VS2017 to VS2022 Co-authored-by: tracyboehrer --- .../deployUseExistResourceGroup/template-BotApp-with-rg.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json index 23a23b1cc..71425ee9a 100644 --- a/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json +++ b/generators/app/templates/echo/{{cookiecutter.bot_name}}/deploymentTemplates/deployUseExistResourceGroup/template-BotApp-with-rg.json @@ -239,7 +239,7 @@ "linuxFxVersion": "PYTHON|3.9", "requestTracingEnabled": false, "remoteDebuggingEnabled": false, - "remoteDebuggingVersion": "VS2017", + "remoteDebuggingVersion": "VS2022", "httpLoggingEnabled": true, "logsDirectorySizeLimit": 35, "detailedErrorLoggingEnabled": false, @@ -275,4 +275,4 @@ } } ] -} \ No newline at end of file +}