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

Enable broker support on Linux for WSL #766

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions msal/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def _main():
instance_discovery=instance_discovery,
enable_broker_on_windows=enable_broker,
enable_broker_on_mac=enable_broker,
enable_broker_on_linux=enable_broker,
enable_pii_log=enable_pii_log,
token_cache=global_cache,
) if not is_cca else msal.ConfidentialClientApplication(
Expand Down
38 changes: 29 additions & 9 deletions msal/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ def _decide_broker(self, allow_broker, enable_pii_log):
"allow_broker is deprecated. "
"Please use PublicClientApplication(..., "
"enable_broker_on_windows=True, "
"enable_broker_on_mac=...)",
"...)",
DeprecationWarning)
opted_in_for_broker = (
self._enable_broker # True means Opted-in from PCA
Expand Down Expand Up @@ -704,7 +704,7 @@ def _decide_broker(self, allow_broker, enable_pii_log):

def is_pop_supported(self):
"""Returns True if this client supports Proof-of-Possession Access Token."""
return self._enable_broker
return self._enable_broker and sys.platform != "linux"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Avoid using logic like "if var != one_thing", because you never know what the rest of things are. In this case, sys.platform has much more values than we should use.

Change this to sys.platform in ("win32", "darwin"). Same applies to other occurrences. Define a new internal helper when appropriate.


def _decorate_scope(
self, scopes,
Expand Down Expand Up @@ -1576,10 +1576,14 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
raise ValueError("auth_scheme is not supported in Cloud Shell")
return self._acquire_token_by_cloud_shell(scopes, data=data)

is_ssh_cert_or_pop_request = (
data.get("token_type") == "ssh-cert" or
data.get("token_type") == "pop" or
isinstance(auth_scheme, msal.auth_scheme.PopAuthScheme))
Comment on lines +1579 to +1582
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are two same snippets in this PR. How about refactoring it into an internal helper like this?

def _is_ssh_cert_or_pop_request(token_type, auth_scheme) -> bool:
    ...

if self._enable_broker and account and account.get("account_source") in (
_GRANT_TYPE_BROKER, # Broker successfully established this account previously.
None, # Unknown data from older MSAL. Broker might still work.
):
) and (sys.platform != "linux" or not is_ssh_cert_or_pop_request):
from .broker import _acquire_token_silently
response = _acquire_token_silently(
"https://{}/{}".format(self.authority.instance, self.authority.tenant),
Expand Down Expand Up @@ -1826,7 +1830,7 @@ def acquire_token_by_username_password(
"""
claims = _merge_claims_challenge_and_capabilities(
self._client_capabilities, claims_challenge)
if self._enable_broker:
if self._enable_broker and sys.platform != "linux":
from .broker import _signin_silently
response = _signin_silently(
"https://{}/{}".format(self.authority.instance, self.authority.tenant),
Expand Down Expand Up @@ -1924,7 +1928,7 @@ def __init__(self, client_id, client_credential=None, **kwargs):

.. note::

You may set enable_broker_on_windows and/or enable_broker_on_mac to True.
You may set enable_broker_on_windows and/or enable_broker_on_mac and/or enable_broker_on_linux to True.

**What is a broker, and why use it?**

Expand Down Expand Up @@ -1952,9 +1956,11 @@ def __init__(self, client_id, client_credential=None, **kwargs):
if your app is expected to run on Windows 10+
* ``msauth.com.msauth.unsignedapp://auth``
if your app is expected to run on Mac
* ``ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id``
if your app is expected to run on Linux, especially WSL

2. installed broker dependency,
e.g. ``pip install msal[broker]>=1.31,<2``.
e.g. ``pip install msal[broker]>=1.32,<2``.

3. tested with ``acquire_token_interactive()`` and ``acquire_token_silent()``.

Expand Down Expand Up @@ -1992,15 +1998,23 @@ def __init__(self, client_id, client_credential=None, **kwargs):
This parameter defaults to None, which means MSAL will not utilize a broker.

New in MSAL Python 1.31.0.

:param boolean enable_broker_on_linux:
This setting is only effective if your app is running on Linux.
This parameter defaults to None, which means MSAL will not utilize a broker.

New in MSAL Python 1.32.0.
"""
if client_credential is not None:
raise ValueError("Public Client should not possess credentials")
# Using kwargs notation for now. We will switch to keyword-only arguments.
enable_broker_on_windows = kwargs.pop("enable_broker_on_windows", False)
enable_broker_on_mac = kwargs.pop("enable_broker_on_mac", False)
enable_broker_on_linux = kwargs.pop("enable_broker_on_linux", False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The dev branch has received new changes. Please merge that into your branch, and then remove this line and add the new parameter into __init__().

self._enable_broker = bool(
enable_broker_on_windows and sys.platform == "win32"
or enable_broker_on_mac and sys.platform == "darwin")
or enable_broker_on_mac and sys.platform == "darwin"
or enable_broker_on_linux and sys.platform == "linux")
super(PublicClientApplication, self).__init__(
client_id, client_credential=None, **kwargs)

Expand Down Expand Up @@ -2129,6 +2143,10 @@ def acquire_token_interactive(
False
) and data.get("token_type") != "ssh-cert" # Work around a known issue as of PyMsalRuntime 0.8
self._validate_ssh_cert_input_data(data)
is_ssh_cert_or_pop_request = (
data.get("token_type") == "ssh-cert" or
data.get("token_type") == "pop" or
isinstance(auth_scheme, msal.auth_scheme.PopAuthScheme))
if not on_before_launching_ui:
on_before_launching_ui = lambda **kwargs: None
if _is_running_in_cloud_shell() and prompt == "none":
Expand All @@ -2137,7 +2155,7 @@ def acquire_token_interactive(
return self._acquire_token_by_cloud_shell(scopes, data=data)
claims = _merge_claims_challenge_and_capabilities(
self._client_capabilities, claims_challenge)
if self._enable_broker:
if self._enable_broker and (sys.platform != "linux" or not is_ssh_cert_or_pop_request):
if parent_window_handle is None:
raise ValueError(
"parent_window_handle is required when you opted into using broker. "
Expand All @@ -2162,7 +2180,9 @@ def acquire_token_interactive(
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

One more thing. MSAL Python's caller (such as Azure CLI) can specify a parameter app.acquire_token_interactive(..., prompt="select_account") which will in turn call pymsalruntime.signin_interactively(). On Windows, that function will pop up the account selector, as requested. But on WSL, it does not pop any UI, and seemingly just return a token for the current user.

Is that expected, @DharshanBJ ? (Note that az login usually expects an account picker.) CC: @jiasli

return self._process_broker_response(response, scopes, data)

if auth_scheme:
if isinstance(auth_scheme, msal.auth_scheme.PopAuthScheme) and sys.platform == "linux":
raise ValueError("POP is not supported on Linux")
elif auth_scheme:
raise ValueError(self._AUTH_SCHEME_UNSUPPORTED)
on_before_launching_ui(ui="browser")
telemetry_context = self._build_telemetry_context(
Expand Down
1 change: 1 addition & 0 deletions msal/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
min_ver = {
"win32": "1.20",
"darwin": "1.31",
"linux": "1.32",
}.get(sys.platform)
if min_ver:
raise ImportError(
Expand Down
2 changes: 1 addition & 1 deletion sample/interactive_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
oidc_authority=os.getenv('OIDC_AUTHORITY'), # For External ID with custom domain
#enable_broker_on_windows=True, # Opted in. You will be guided to meet the prerequisites, if your app hasn't already
#enable_broker_on_mac=True, # Opted in. You will be guided to meet the prerequisites, if your app hasn't already

#enable_broker_on_linux=True, # Opted in. You will be guided to meet the prerequisites, if your app hasn't already
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
# If absent, ClientApplication will create its own empty token cache
)
Expand Down
6 changes: 4 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ broker =
# most existing MSAL Python apps do not have the redirect_uri needed by broker.
#
# We need pymsalruntime.CallbackData introduced in PyMsalRuntime 0.14
pymsalruntime>=0.14,<0.18; python_version>='3.6' and platform_system=='Windows'
pymsalruntime>=0.14,<0.19; python_version>='3.6' and platform_system=='Windows'
# On Mac, PyMsalRuntime 0.17+ is expected to support SSH cert and ROPC
pymsalruntime>=0.17,<0.18; python_version>='3.8' and platform_system=='Darwin'
pymsalruntime>=0.17,<0.19; python_version>='3.8' and platform_system=='Darwin'
# PyMsalRuntime 0.18+ is expected to support broker on Linux
pymsalruntime>=0.18,<0.19; python_version>='3.8' and platform_system=='Linux'

[options.packages.find]
exclude =
Expand Down
4 changes: 3 additions & 1 deletion tests/broker-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
_AZURE_CLI,
authority="https://login.microsoftonline.com/organizations",
enable_broker_on_mac=True,
enable_broker_on_windows=True)
enable_broker_on_windows=True,
enable_broker_on_linux=True,
)

def interactive_and_silent(scopes, auth_scheme, data, expected_token_type):
print("An account picker shall be pop up, possibly behind this console. Continue from there.")
Expand Down
1 change: 1 addition & 0 deletions tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def _build_app(cls,
http_client=http_client or MinimalHttpClient(),
enable_broker_on_windows=broker_available,
enable_broker_on_mac=broker_available,
enable_broker_on_linux=broker_available,
Copy link
Collaborator

Choose a reason for hiding this comment

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

praise: Thanks for finding this test file!

suggestion: If you haven't already, please also do python -m unittest tests.test_e2e and make sure we won't run into any issue. Note that you will need to setup an .env first, whose content is described at the beginning of this test_e2e.py file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't support POP and ssh-cert flows for linux yet, tested the other e2e flows.

)

def _test_username_password(self,
Expand Down