Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

{Core} Refactor code for MSAL authentication #29439

Merged
merged 1 commit into from
Aug 30, 2024
Merged

Conversation

jiasli
Copy link
Member

@jiasli jiasli commented Jul 22, 2024

Related command
az login

Description

Change 1: Refactor UserCredential and ServicePrincipalCredential

In the current code,

  • UserCredential inherits from msal.PublicClientApplication
  • ServicePrincipalCredential inherits from msal.ConfidentialClientApplication

This exposes the internal MSAL application instances to the caller SDK which only requires the get_token() callback interface.

This PR stops UserCredential and ServicePrincipalCredential from inheriting from MSAL, but makes them keep an internal MSAL application instance, in order to greatly reduce MSAL applications' exposure.

Change 2: Refactor ServicePrincipalAuth

All attributes are set at __init__() to avoid frequently calling getattr() to check the existence of attributes. The logic for generating client_credential is moved to ServicePrincipalAuth.get_msal_client_credential().

This PR paves way for

Copy link

azure-client-tools-bot-prd bot commented Jul 22, 2024

️✔️AzureCLI-FullTest
️✔️acr
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️acs
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️advisor
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️ams
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️apim
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️appconfig
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️appservice
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️aro
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️backup
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️batch
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️batchai
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️billing
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️botservice
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️cdn
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️cloud
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️cognitiveservices
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️compute_recommender
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️config
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️configure
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️consumption
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️container
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️containerapp
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️core
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️cosmosdb
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️databoxedge
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️dla
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️dls
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️dms
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️eventgrid
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️eventhubs
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️feedback
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️find
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️hdinsight
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️identity
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️iot
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️keyvault
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️kusto
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️lab
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️managedservices
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️maps
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️marketplaceordering
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️monitor
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️mysql
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️netappfiles
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️network
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️policyinsights
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️privatedns
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️profile
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️rdbms
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️redis
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️relay
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️resource
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️role
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️search
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️security
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️servicebus
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️serviceconnector
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️servicefabric
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️signalr
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️sql
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️sqlvm
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️storage
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️synapse
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️telemetry
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️util
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9
️✔️vm
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.12
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.12
️✔️3.9

Copy link

Hi @jiasli,
Since the current milestone time is less than 7 days, this pr will be reviewed in the next milestone.

Copy link

azure-client-tools-bot-prd bot commented Jul 22, 2024

️✔️AzureCLI-BreakingChangeTest
️✔️Non Breaking Changes

@yonzhan
Copy link
Collaborator

yonzhan commented Jul 22, 2024

Core

self.authority.tenant, claims)
result = self.acquire_token_silent_with_error(list(scopes), self._account, claims_challenge=claims, **kwargs)
self._msal_app.authority.tenant, claims)
result = self._msal_app.acquire_token_silent_with_error(list(scopes), self._account, claims_challenge=claims,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By saving msal.PublicClientApplication instance to self._msal_app, self._account is no longer saved as an attribute of msal.PublicClientApplication. This avoids the possible conflict of msal.PublicClientApplication introducing an _account attribute of its own.

Comment on lines -185 to -186
match = re.search(r'-+BEGIN CERTIFICATE-+(?P<public>[^-]+)-+END CERTIFICATE-+', cert_file_string, re.I)
public_certificate = match.group().strip()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not test code with the code itself:

match = re.search(r'-----BEGIN CERTIFICATE-----(?P<cert_value>[^-]+)-----END CERTIFICATE-----',
self._certificate_string, re.I)
self._public_certificate = match.group()


class TestIdentity(unittest.TestCase):

@mock.patch("azure.cli.core.auth.identity.ServicePrincipalStore.save_entry")
@mock.patch("msal.application.ConfidentialClientApplication.acquire_token_for_client")
@mock.patch("msal.application.ConfidentialClientApplication.__init__")
@mock.patch("msal.application.ConfidentialClientApplication.__init__", return_value=None)
Copy link
Member Author

@jiasli jiasli Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msal.application.ConfidentialClientApplication.__init__ is previously called as super().__init__ in azure.cli.core.auth.msal_authentication.ServicePrincipalCredential.__init__. The returned MagicMock gets discarded.

Now msal.application.ConfidentialClientApplication.__init__ is directly invoked. Without return_value=None, patching msal.application.ConfidentialClientApplication.__init__ makes it return a MagicMock. pytest fails:

self = <azure.cli.core.auth.identity.Identity object at 0x0000021CEACFC150>
client_id = 'sp_id1', credential = {'client_secret': 'test_secret'}
scopes = 'openid'

    def login_with_service_principal(self, client_id, credential, scopes):
        """
        `credential` is a dict returned by ServicePrincipalAuth.build_credential
        """
        sp_auth = ServicePrincipalAuth.build_from_credential(self.tenant_id, client_id, credential)
        client_credential = sp_auth.get_msal_client_credential()
>       cca = ConfidentialClientApplication(client_id, client_credential, **self._msal_app_kwargs)
E       TypeError: __init__() should return None, not 'MagicMock'

..\identity.py:196: TypeError

@jiasli jiasli marked this pull request as ready for review August 6, 2024 12:23
Comment on lines +195 to +197
client_credential = sp_auth.get_msal_client_credential()
cca = ConfidentialClientApplication(client_id, client_credential, **self._msal_app_kwargs)
result = cca.acquire_token_for_client(scopes)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because ServicePrincipalCredential stops inheriting from msal.ConfidentialClientApplication, it is no longer possible to call acquire_token_for_client on the cred. We prepare client_credential and directly create a ConfidentialClientApplication instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is renamed to more accurately reflect its content.

logger = get_logger(__name__)


class UserCredential(PublicClientApplication):
class UserCredential: # pylint: disable=too-few-public-methods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, pylint doesn't think this is worth a class. Haha

Used when class has too few public methods, so be sure it's really worth it.

Ref: https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-few-public-methods.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to define such class so that it can be passed to SDK as a credential.

@jiasli jiasli merged commit 0a35d5f into Azure:dev Aug 30, 2024
65 checks passed
@jiasli jiasli deleted the msal-auth branch August 30, 2024 06:48
@jiasli
Copy link
Member Author

jiasli commented Sep 2, 2024

This PR breaks connectedk8s extension as azure.cli.core.auth.msal_authentication.ServicePrincipalCredential can no longer be imported due to the msal_authentication.py -> msal_credentials.py renaming:

https://github.com/Azure/azure-cli-extensions/blob/ae516009b35926d2c4bbb7b6eaf8e1e30fc164ed/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L21

from azure.cli.core.auth.msal_authentication import ServicePrincipalCredential

ServicePrincipalCredential is used to work around AzureAD/microsoft-authentication-library-for-python#692:

https://github.com/Azure/azure-cli-extensions/blob/ae516009b35926d2c4bbb7b6eaf8e1e30fc164ed/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L95-L100

        credential, _, _ = profile.get_login_credentials(subscription_id=profile.get_subscription()["id"],
                                                         resource=consts.KAP_1P_Server_App_Scope)
        if isinstance(credential._credential, ServicePrincipalCredential):
            # This is a workaround to fix the issue where the token is not being refreshed
            # https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/692
            credential._credential.remove_tokens_for_client()

This is considered a bad practice as internal credential._credential attribute shouldn't be accessed by other modules/extensions.

As AzureAD/microsoft-authentication-library-for-python#692 has been resolved, this workaround should no longer be necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Account az login/account Auto-Assign Auto assign by bot Core CLI core infrastructure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants