From 91b42cc0770df3f93f5a0ea6981db7135a5954f7 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 12 Jul 2019 13:34:15 -0700 Subject: [PATCH 1/5] _internal -> _managed_identity --- .../azure/identity/{_internal.py => _managed_identity.py} | 0 .../azure/identity/aio/{_internal.py => _managed_identity.py} | 2 +- sdk/identity/azure-identity/azure/identity/aio/credentials.py | 2 +- sdk/identity/azure-identity/azure/identity/credentials.py | 2 +- sdk/identity/azure-identity/tests/test_identity.py | 2 +- sdk/identity/azure-identity/tests/test_identity_async.py | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename sdk/identity/azure-identity/azure/identity/{_internal.py => _managed_identity.py} (100%) rename sdk/identity/azure-identity/azure/identity/aio/{_internal.py => _managed_identity.py} (99%) diff --git a/sdk/identity/azure-identity/azure/identity/_internal.py b/sdk/identity/azure-identity/azure/identity/_managed_identity.py similarity index 100% rename from sdk/identity/azure-identity/azure/identity/_internal.py rename to sdk/identity/azure-identity/azure/identity/_managed_identity.py diff --git a/sdk/identity/azure-identity/azure/identity/aio/_internal.py b/sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py similarity index 99% rename from sdk/identity/azure-identity/azure/identity/aio/_internal.py rename to sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py index 4f502e95ed17..ec698b52c98b 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_internal.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_managed_identity.py @@ -12,7 +12,7 @@ from ._authn_client import AsyncAuthnClient from ..constants import Endpoints, EnvironmentVariables -from .._internal import _ManagedIdentityBase +from .._managed_identity import _ManagedIdentityBase class _AsyncManagedIdentityBase(_ManagedIdentityBase): diff --git a/sdk/identity/azure-identity/azure/identity/aio/credentials.py b/sdk/identity/azure-identity/azure/identity/aio/credentials.py index 7f9f846f9ea1..c43dc917e518 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/aio/credentials.py @@ -14,7 +14,7 @@ from azure.core.pipeline.policies import ContentDecodePolicy, HeadersPolicy, NetworkTraceLoggingPolicy, AsyncRetryPolicy from ._authn_client import AsyncAuthnClient -from ._internal import ImdsCredential, MsiCredential +from ._managed_identity import ImdsCredential, MsiCredential from .._base import ClientSecretCredentialBase, CertificateCredentialBase from ..constants import Endpoints, EnvironmentVariables from ..credentials import ChainedTokenCredential diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index 6a172995d563..d53edf8e2c62 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -14,7 +14,7 @@ from ._authn_client import AuthnClient from ._base import ClientSecretCredentialBase, CertificateCredentialBase -from ._internal import ImdsCredential, MsiCredential +from ._managed_identity import ImdsCredential, MsiCredential from .constants import Endpoints, EnvironmentVariables try: diff --git a/sdk/identity/azure-identity/tests/test_identity.py b/sdk/identity/azure-identity/tests/test_identity.py index 72d9a31dbf0a..c9756ad3b344 100644 --- a/sdk/identity/azure-identity/tests/test_identity.py +++ b/sdk/identity/azure-identity/tests/test_identity.py @@ -22,7 +22,7 @@ ManagedIdentityCredential, ChainedTokenCredential, ) -from azure.identity._internal import ImdsCredential +from azure.identity._managed_identity import ImdsCredential from azure.identity.constants import EnvironmentVariables from helpers import mock_response, Request, validating_transport diff --git a/sdk/identity/azure-identity/tests/test_identity_async.py b/sdk/identity/azure-identity/tests/test_identity_async.py index 78230c94bb09..ba203cd2eb59 100644 --- a/sdk/identity/azure-identity/tests/test_identity_async.py +++ b/sdk/identity/azure-identity/tests/test_identity_async.py @@ -19,7 +19,7 @@ EnvironmentCredential, ManagedIdentityCredential, ) -from azure.identity.aio._internal import ImdsCredential +from azure.identity.aio._managed_identity import ImdsCredential from azure.identity.constants import EnvironmentVariables from helpers import mock_response, Request, async_validating_transport From 63b4d81a292ed44542bd49dc15728b6bff59e63b Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Mon, 15 Jul 2019 13:01:28 -0700 Subject: [PATCH 2/5] credential wrapping MSAL's ConfidentialClientApplication --- .../azure/identity/_internal/__init__.py | 6 ++ .../confidential_client_credential.py | 68 +++++++++++++++ .../_internal/msal_transport_adapter.py | 87 +++++++++++++++++++ .../azure-identity/tests/test_live.py | 22 +++-- 4 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 sdk/identity/azure-identity/azure/identity/_internal/__init__.py create mode 100644 sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py create mode 100644 sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py diff --git a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py new file mode 100644 index 000000000000..dff4c6986cb8 --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py @@ -0,0 +1,6 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from .confidential_client_credential import ConfidentialClientCredential +from .msal_transport_adapter import MsalTransportResponse, MsalTransportAdapter diff --git a/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py b/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py new file mode 100644 index 000000000000..73bb7d78cb90 --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py @@ -0,0 +1,68 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""A credential which wraps an MSAL ConfidentialClientApplication and delegates token acquisition and caching to it. +This entails monkeypatching MSAL's OAuth client with an adapter substituting an azure-core pipeline for Requests. +""" + +import time + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +try: + from unittest import mock +except ImportError: # python < 3.3 + import mock # type: ignore + +if TYPE_CHECKING: + # pylint:disable=unused-import + from typing import Any, Mapping, Optional, Union + +from azure.core.credentials import AccessToken +import msal + +from .msal_transport_adapter import MsalTransportAdapter + + +class ConfidentialClientCredential(MsalTransportAdapter): + """Wraps an MSAL ConfidentialClientApplication with the TokenCredential API""" + + def __init__(self, client_id, client_credential, authority, **kwargs): + # type: (str, str, Union[str, Mapping[str, str]], Any) -> None + super(ConfidentialClientCredential, self).__init__(**kwargs) + + self._client_id = client_id + self._client_credential = client_credential + self._authority = authority + + # postpone creating the wrapped application because its initializer uses the network + self._app = None # type: Optional[msal.ConfidentialClientApplication] + + def get_token(self, *scopes): + # type: (str) -> AccessToken + + if not self._app: + self._app = self._create_msal_application() + + # MSAL requires scopes be a list + scopes = list(scopes) # type: ignore + now = int(time.time()) + + # First try to get a cached access token or if a refresh token is cached, redeem it for an access token. + # Failing that, acquire a new token. + result = self._app.acquire_token_silent(scopes, account=None) or self._app.acquire_token_for_client(scopes) + return AccessToken(result["access_token"], now + int(result["expires_in"])) + + def _create_msal_application(self): + # ConfidentialClientApplication's initializer uses msal.authority to send requests to AAD + with mock.patch("msal.authority.requests", self): + app = msal.ConfidentialClientApplication( + client_id=self._client_id, client_credential=self._client_credential, authority=self._authority + ) + # replace the client's requests.Session with adapter + app.client.session = self + return app diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py new file mode 100644 index 000000000000..2254a200dc4f --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py @@ -0,0 +1,87 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Adapter to substitute an azure-core pipeline for Requests in MSAL application token acquisition methods.""" + +import json + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + # pylint:disable=unused-import + from typing import Any, Dict, Mapping, Optional + from azure.core.pipeline import PipelineResponse + +from azure.core.configuration import Configuration +from azure.core.exceptions import ClientAuthenticationError +from azure.core.pipeline import Pipeline +from azure.core.pipeline.policies import ContentDecodePolicy, NetworkTraceLoggingPolicy, RetryPolicy +from azure.core.pipeline.transport import HttpRequest, RequestsTransport + + +class MsalTransportResponse: + """Wraps an azure-core PipelineResponse with the shape of requests.Response""" + + def __init__(self, pipeline_response): + # type: (PipelineResponse) -> None + self._response = pipeline_response.http_response + self.status_code = self._response.status_code + self.text = self._response.text() + + def json(self, **kwargs): + # type: (Any) -> Mapping[str, Any] + return json.loads(self.text, **kwargs) + + def raise_for_status(self): + # type: () -> None + raise ClientAuthenticationError("authentication failed", self._response) + + +class MsalTransportAdapter: + """Wraps an azure-core pipeline with the shape of requests.Session""" + + def __init__(self, **kwargs): + # type: (Any) -> None + self._pipeline = self._build_pipeline(**kwargs) + + @staticmethod + def create_config(**kwargs): + # type: (Any) -> Configuration + config = Configuration(**kwargs) + config.logging_policy = NetworkTraceLoggingPolicy(**kwargs) + config.retry_policy = RetryPolicy(**kwargs) + return config + + def _build_pipeline(self, config=None, policies=None, transport=None, **kwargs): + config = config or self.create_config(**kwargs) + policies = policies or [ContentDecodePolicy(), config.retry_policy, config.logging_policy] + if not transport: + transport = RequestsTransport(configuration=config) + return Pipeline(transport=transport, policies=policies) + + def get(self, url, headers=None, params=None, timeout=None, verify=None, **kwargs): + # type: (str, Optional[Mapping[str, str]], Optional[Dict[str, str]], float, bool, Any) -> MsalTransportResponse + request = HttpRequest("GET", url, headers=headers) + if params: + request.format_parameters(params) + response = self._pipeline.run( + request, stream=False, connection_timeout=timeout, connection_verify=verify, **kwargs + ) + return MsalTransportResponse(response) + + def post(self, url, data=None, headers=None, params=None, timeout=None, verify=None, **kwargs): + # type: (str, Optional[Mapping[str, str]], Optional[Mapping[str, str]], Optional[Dict[str, str]], float, bool, Any) -> MsalTransportResponse + request = HttpRequest("POST", url, headers=headers) + if params: + request.format_parameters(params) + if data: + request.headers["Content-Type"] = "application/x-www-form-urlencoded" + request.set_formdata_body(data) + response = self._pipeline.run( + request, stream=False, connection_timeout=timeout, connection_verify=verify, **kwargs + ) + return MsalTransportResponse(response) diff --git a/sdk/identity/azure-identity/tests/test_live.py b/sdk/identity/azure-identity/tests/test_live.py index 891524a48929..ddff3d83fa3a 100644 --- a/sdk/identity/azure-identity/tests/test_live.py +++ b/sdk/identity/azure-identity/tests/test_live.py @@ -2,16 +2,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -import os - -try: - from unittest import mock -except ImportError: # python < 3.3 - import mock # type: ignore - from azure.identity import DefaultAzureCredential, CertificateCredential, ClientSecretCredential -from azure.identity.constants import EnvironmentVariables -import pytest +from azure.identity._internal import ConfidentialClientCredential ARM_SCOPE = "https://management.azure.com/.default" @@ -46,3 +38,15 @@ def test_default_credential(live_identity_settings): assert token assert token.token assert token.expires_on + + +def test_confidential_client_credential(live_identity_settings): + credential = ConfidentialClientCredential( + client_id=live_identity_settings["client_id"], + client_credential=live_identity_settings["client_secret"], + authority="https://login.microsoftonline.com/" + live_identity_settings["tenant_id"], + ) + token = credential.get_token(ARM_SCOPE) + assert token + assert token.token + assert token.expires_on From 979769d2cd374f8415a236e564bd698c8b4ee560 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 16 Jul 2019 10:36:06 -0700 Subject: [PATCH 3/5] extract base class --- .../azure/identity/_internal/__init__.py | 2 +- .../confidential_client_credential.py | 68 --------------- .../identity/_internal/msal_credentials.py | 84 +++++++++++++++++++ .../_internal/msal_transport_adapter.py | 3 +- 4 files changed, 87 insertions(+), 70 deletions(-) delete mode 100644 sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py create mode 100644 sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py diff --git a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py index dff4c6986cb8..9ea29a25784d 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py @@ -2,5 +2,5 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from .confidential_client_credential import ConfidentialClientCredential +from .msal_credentials import ConfidentialClientCredential from .msal_transport_adapter import MsalTransportResponse, MsalTransportAdapter diff --git a/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py b/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py deleted file mode 100644 index 73bb7d78cb90..000000000000 --- a/sdk/identity/azure-identity/azure/identity/_internal/confidential_client_credential.py +++ /dev/null @@ -1,68 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""A credential which wraps an MSAL ConfidentialClientApplication and delegates token acquisition and caching to it. -This entails monkeypatching MSAL's OAuth client with an adapter substituting an azure-core pipeline for Requests. -""" - -import time - -try: - from typing import TYPE_CHECKING -except ImportError: - TYPE_CHECKING = False - -try: - from unittest import mock -except ImportError: # python < 3.3 - import mock # type: ignore - -if TYPE_CHECKING: - # pylint:disable=unused-import - from typing import Any, Mapping, Optional, Union - -from azure.core.credentials import AccessToken -import msal - -from .msal_transport_adapter import MsalTransportAdapter - - -class ConfidentialClientCredential(MsalTransportAdapter): - """Wraps an MSAL ConfidentialClientApplication with the TokenCredential API""" - - def __init__(self, client_id, client_credential, authority, **kwargs): - # type: (str, str, Union[str, Mapping[str, str]], Any) -> None - super(ConfidentialClientCredential, self).__init__(**kwargs) - - self._client_id = client_id - self._client_credential = client_credential - self._authority = authority - - # postpone creating the wrapped application because its initializer uses the network - self._app = None # type: Optional[msal.ConfidentialClientApplication] - - def get_token(self, *scopes): - # type: (str) -> AccessToken - - if not self._app: - self._app = self._create_msal_application() - - # MSAL requires scopes be a list - scopes = list(scopes) # type: ignore - now = int(time.time()) - - # First try to get a cached access token or if a refresh token is cached, redeem it for an access token. - # Failing that, acquire a new token. - result = self._app.acquire_token_silent(scopes, account=None) or self._app.acquire_token_for_client(scopes) - return AccessToken(result["access_token"], now + int(result["expires_in"])) - - def _create_msal_application(self): - # ConfidentialClientApplication's initializer uses msal.authority to send requests to AAD - with mock.patch("msal.authority.requests", self): - app = msal.ConfidentialClientApplication( - client_id=self._client_id, client_credential=self._client_credential, authority=self._authority - ) - # replace the client's requests.Session with adapter - app.client.session = self - return app diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py new file mode 100644 index 000000000000..ca74a37f1ed5 --- /dev/null +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -0,0 +1,84 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Credentials wrapping MSAL applications and delegating token acquisition and caching to them. +This entails monkeypatching MSAL's OAuth client with an adapter substituting an azure-core pipeline for Requests. +""" + +import time + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +try: + from unittest import mock +except ImportError: # python < 3.3 + import mock # type: ignore + +if TYPE_CHECKING: + # pylint:disable=unused-import + from typing import Any, Mapping, Optional, Union + +from azure.core.credentials import AccessToken +import msal + +from .msal_transport_adapter import MsalTransportAdapter + + +class MsalCredential(MsalTransportAdapter): + """Base class for credentials wrapping MSAL applications""" + + def __init__(self, client_id, authority, app_class, client_credential=None, **kwargs): + # type: (str, str, msal.ClientApplication, Optional[Union[str, Mapping[str, str]]], Any) -> None + super(MsalCredential, self).__init__(**kwargs) + + self._authority = authority + self._client_credential = client_credential + self._client_id = client_id + + # postpone creating the wrapped application because its initializer uses the network + self._app_class = app_class + self._msal_app = None # type: Optional[msal.ClientApplication] + + @property + def _app(self): + # type: () -> msal.ClientApplication + """The wrapped MSAL application""" + + if not self._msal_app: + # MSAL application initializers use msal.authority to send AAD tenant discovery requests + with mock.patch("msal.authority.requests", self): + app = self._app_class( + client_id=self._client_id, client_credential=self._client_credential, authority=self._authority + ) + + # monkeypatch the app to replace requests.Session with MsalTransportAdapter + app.client.session = self + self._msal_app = app + + return self._msal_app + + +class ConfidentialClientCredential(MsalCredential): + """Wraps an MSAL ConfidentialClientApplication with the TokenCredential API""" + + def __init__(self, **kwargs): + # type: (Any) -> None + super(ConfidentialClientCredential, self).__init__(app_class=msal.ConfidentialClientApplication, **kwargs) + + def get_token(self, *scopes): + # type: (str) -> AccessToken + + # MSAL requires scopes be a list + scopes = list(scopes) # type: ignore + now = int(time.time()) + + # First try to get a cached access token or if a refresh token is cached, redeem it for an access token. + # Failing that, acquire a new token. + app = self._app # type: msal.ConfidentialClientApplication + result = app.acquire_token_silent(scopes, account=None) or app.acquire_token_for_client(scopes) + + return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py index 2254a200dc4f..1a19beaf1c3d 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_transport_adapter.py @@ -41,11 +41,12 @@ def raise_for_status(self): raise ClientAuthenticationError("authentication failed", self._response) -class MsalTransportAdapter: +class MsalTransportAdapter(object): """Wraps an azure-core pipeline with the shape of requests.Session""" def __init__(self, **kwargs): # type: (Any) -> None + super(MsalTransportAdapter, self).__init__() self._pipeline = self._build_pipeline(**kwargs) @staticmethod From abca1022458aff46b0b13ab56db9f952c0fc394e Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 16 Jul 2019 10:41:40 -0700 Subject: [PATCH 4/5] raise when authentication fails --- .../azure/identity/_internal/msal_credentials.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index ca74a37f1ed5..df5da38c317f 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -23,6 +23,7 @@ from typing import Any, Mapping, Optional, Union from azure.core.credentials import AccessToken +from azure.core.exceptions import ClientAuthenticationError import msal from .msal_transport_adapter import MsalTransportAdapter @@ -81,4 +82,7 @@ def get_token(self, *scopes): app = self._app # type: msal.ConfidentialClientApplication result = app.acquire_token_silent(scopes, account=None) or app.acquire_token_for_client(scopes) + if "access_token" not in result: + raise ClientAuthenticationError(message="authentication failed: {}".format(result.get("error_description"))) + return AccessToken(result["access_token"], now + int(result["expires_in"])) From 5e27816154a6e1860d4454b9720a98d8d9b0298c Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 17 Jul 2019 13:08:06 -0700 Subject: [PATCH 5/5] compose rather than inherit --- .../azure/identity/_internal/msal_credentials.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index df5da38c317f..9bf44cbb3219 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -29,17 +29,17 @@ from .msal_transport_adapter import MsalTransportAdapter -class MsalCredential(MsalTransportAdapter): +class MsalCredential(object): """Base class for credentials wrapping MSAL applications""" def __init__(self, client_id, authority, app_class, client_credential=None, **kwargs): # type: (str, str, msal.ClientApplication, Optional[Union[str, Mapping[str, str]]], Any) -> None - super(MsalCredential, self).__init__(**kwargs) - self._authority = authority self._client_credential = client_credential self._client_id = client_id + self._adapter = kwargs.pop("msal_adapter", None) or MsalTransportAdapter(**kwargs) + # postpone creating the wrapped application because its initializer uses the network self._app_class = app_class self._msal_app = None # type: Optional[msal.ClientApplication] @@ -51,13 +51,13 @@ def _app(self): if not self._msal_app: # MSAL application initializers use msal.authority to send AAD tenant discovery requests - with mock.patch("msal.authority.requests", self): + with mock.patch("msal.authority.requests", self._adapter): app = self._app_class( client_id=self._client_id, client_credential=self._client_credential, authority=self._authority ) # monkeypatch the app to replace requests.Session with MsalTransportAdapter - app.client.session = self + app.client.session = self._adapter self._msal_app = app return self._msal_app