Skip to content

Commit

Permalink
Merge branch 'dev' into mi-arc-detection
Browse files Browse the repository at this point in the history
  • Loading branch information
ashok672 authored Nov 6, 2024
2 parents e9b3913 + 7db6c2c commit 86f448b
Show file tree
Hide file tree
Showing 14 changed files with 617 additions and 133 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ docs/_build/
# The test configuration file(s) could potentially contain credentials
tests/config.json

# Token Cache files
msal_cache.bin

.env
.perf.baseline
1 change: 1 addition & 0 deletions msal/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ def _main():
authority=authority,
instance_discovery=instance_discovery,
enable_broker_on_windows=enable_broker,
enable_broker_on_mac=enable_broker,
enable_pii_log=enable_pii_log,
token_cache=global_cache,
) if not is_cca else msal.ConfidentialClientApplication(
Expand Down
115 changes: 88 additions & 27 deletions msal/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import warnings
from threading import Lock
from typing import Optional # Needed in Python 3.7 & 3.8
import os

from .oauth2cli import Client, JwtAssertionCreator
Expand All @@ -21,11 +22,16 @@


# The __init__.py will import this. Not the other way around.
__version__ = "1.30.0" # When releasing, also check and bump our dependencies's versions if needed
__version__ = "1.31.0" # When releasing, also check and bump our dependencies's versions if needed

logger = logging.getLogger(__name__)
_AUTHORITY_TYPE_CLOUDSHELL = "CLOUDSHELL"

def _init_broker(enable_pii_log): # Make it a function to allow mocking
from . import broker # Trigger Broker's initialization, lazily
if enable_pii_log:
broker._enable_pii_log()

def extract_certs(public_cert_content):
# Parses raw public certificate file contents and returns a list of strings
# Usage: headers = {"x5c": extract_certs(open("my_cert.pem").read())}
Expand Down Expand Up @@ -189,6 +195,21 @@ def obtain_token_by_username_password(self, username, password, **kwargs):
username, password, headers=headers, **kwargs)


def _msal_extension_check():
# Can't run this in module or class level otherwise you'll get circular import error
try:
from msal_extensions import __version__ as v
major, minor, _ = v.split(".", maxsplit=3)
if not (int(major) >= 1 and int(minor) >= 2):
warnings.warn(
"Please upgrade msal-extensions. "
"Only msal-extensions 1.2+ can work with msal 1.30+")
except ImportError:
pass # The optional msal_extensions is not installed. Business as usual.
except ValueError:
logger.exception(f"msal_extensions version {v} not in major.minor.patch format")


class ClientApplication(object):
"""You do not usually directly use this class. Use its subclasses instead:
:class:`PublicClientApplication` and :class:`ConfidentialClientApplication`.
Expand All @@ -205,6 +226,7 @@ class ClientApplication(object):
REMOVE_ACCOUNT_ID = "903"

ATTEMPT_REGION_DISCOVERY = True # "TryAutoDetect"
DISABLE_MSAL_FORCE_REGION = False # Used in azure_region to disable MSAL_FORCE_REGION behavior
_TOKEN_SOURCE = "token_source"
_TOKEN_SOURCE_IDP = "identity_provider"
_TOKEN_SOURCE_CACHE = "cache"
Expand Down Expand Up @@ -411,9 +433,11 @@ def __init__(
(STS) what this client is capable for,
so STS can decide to turn on certain features.
For example, if client is capable to handle *claims challenge*,
STS can then issue CAE access tokens to resources
knowing when the resource emits *claims challenge*
the client will be capable to handle.
STS may issue
`Continuous Access Evaluation (CAE) <https://learn.microsoft.com/entra/identity/conditional-access/concept-continuous-access-evaluation>`_
access tokens to resources,
knowing that when the resource emits a *claims challenge*
the client will be able to handle those challenges.
Implementation details:
Client capability is implemented using "claims" parameter on the wire,
Expand All @@ -426,11 +450,14 @@ def __init__(
Instructs MSAL to use the Entra regional token service. This legacy feature is only available to
first-party applications. Only ``acquire_token_for_client()`` is supported.
Supports 3 values:
Supports 4 values:
``azure_region=None`` - meaning no region is used. This is the default value.
``azure_region="some_region"`` - meaning the specified region is used.
``azure_region=True`` - meaning MSAL will try to auto-detect the region. This is not recommended.
1. ``azure_region=None`` - This default value means no region is configured.
MSAL will use the region defined in env var ``MSAL_FORCE_REGION``.
2. ``azure_region="some_region"`` - meaning the specified region is used.
3. ``azure_region=True`` - meaning
MSAL will try to auto-detect the region. This is not recommended.
4. ``azure_region=False`` - meaning MSAL will use no region.
.. note::
Region auto-discovery has been tested on VMs and on Azure Functions. It is unreliable.
Expand Down Expand Up @@ -608,7 +635,10 @@ def __init__(
except ValueError: # Those are explicit authority validation errors
raise
except Exception: # The rest are typically connection errors
if validate_authority and azure_region and not oidc_authority:
if validate_authority and not oidc_authority and (
azure_region # Opted in to use region
or (azure_region is None and os.getenv("MSAL_FORCE_REGION")) # Will use region
):
# Since caller opts in to use region, here we tolerate connection
# errors happened during authority validation at non-region endpoint
self.authority = Authority(
Expand All @@ -628,6 +658,8 @@ def __init__(
self.authority_groups = None
self._telemetry_buffer = {}
self._telemetry_lock = Lock()
_msal_extension_check()


def _decide_broker(self, allow_broker, enable_pii_log):
is_confidential_app = self.client_credential or isinstance(
Expand All @@ -638,20 +670,28 @@ def _decide_broker(self, allow_broker, enable_pii_log):
if allow_broker:
warnings.warn(
"allow_broker is deprecated. "
"Please use PublicClientApplication(..., enable_broker_on_windows=True)",
"Please use PublicClientApplication(..., "
"enable_broker_on_windows=True, "
"enable_broker_on_mac=...)",
DeprecationWarning)
self._enable_broker = self._enable_broker or (
opted_in_for_broker = (
self._enable_broker # True means Opted-in from PCA
or (
# When we started the broker project on Windows platform,
# the allow_broker was meant to be cross-platform. Now we realize
# that other platforms have different redirect_uri requirements,
# so the old allow_broker is deprecated and will only for Windows.
allow_broker and sys.platform == "win32")
if (self._enable_broker and not is_confidential_app
and not self.authority.is_adfs and not self.authority._is_b2c):
)
self._enable_broker = ( # This same variable will also store the state
opted_in_for_broker
and not is_confidential_app
and not self.authority.is_adfs
and not self.authority._is_b2c
)
if self._enable_broker:
try:
from . import broker # Trigger Broker's initialization
if enable_pii_log:
broker._enable_pii_log()
_init_broker(enable_pii_log)
except RuntimeError:
self._enable_broker = False
logger.exception(
Expand Down Expand Up @@ -692,9 +732,11 @@ def _build_telemetry_context(
self._telemetry_buffer, self._telemetry_lock, api_id,
correlation_id=correlation_id, refresh_reason=refresh_reason)

def _get_regional_authority(self, central_authority):
if not self._region_configured: # User did not opt-in to ESTS-R
def _get_regional_authority(self, central_authority) -> Optional[Authority]:
if self._region_configured is False: # User opts out of ESTS-R
return None # Short circuit to completely bypass region detection
if self._region_configured is None: # User did not make an ESTS-R choice
self._region_configured = os.getenv("MSAL_FORCE_REGION") or None
self._region_detected = self._region_detected or _detect_region(
self.http_client if self._region_configured is not None else None)
if (self._region_configured != self.ATTEMPT_REGION_DISCOVERY
Expand Down Expand Up @@ -1879,7 +1921,7 @@ def __init__(self, client_id, client_credential=None, **kwargs):
.. note::
You may set enable_broker_on_windows to True.
You may set enable_broker_on_windows and/or enable_broker_on_mac to True.
**What is a broker, and why use it?**
Expand All @@ -1905,9 +1947,11 @@ def __init__(self, client_id, client_credential=None, **kwargs):
* ``ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id``
if your app is expected to run on Windows 10+
* ``msauth.com.msauth.unsignedapp://auth``
if your app is expected to run on Mac
2. installed broker dependency,
e.g. ``pip install msal[broker]>=1.25,<2``.
e.g. ``pip install msal[broker]>=1.31,<2``.
3. tested with ``acquire_token_interactive()`` and ``acquire_token_silent()``.
Expand Down Expand Up @@ -1939,12 +1983,21 @@ 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.25.0.
:param boolean enable_broker_on_mac:
This setting is only effective if your app is running on Mac.
This parameter defaults to None, which means MSAL will not utilize a broker.
New in MSAL Python 1.31.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)
self._enable_broker = enable_broker_on_windows and sys.platform == "win32"
enable_broker_on_mac = kwargs.pop("enable_broker_on_mac", False)
self._enable_broker = bool(
enable_broker_on_windows and sys.platform == "win32"
or enable_broker_on_mac and sys.platform == "darwin")
super(PublicClientApplication, self).__init__(
client_id, client_credential=None, **kwargs)

Expand Down Expand Up @@ -2022,14 +2075,22 @@ def acquire_token_interactive(
New in version 1.15.
:param int parent_window_handle:
Required if your app is running on Windows and opted in to use broker.
OPTIONAL.
* If your app does not opt in to use broker,
you do not need to provide a ``parent_window_handle`` here.
* If your app opts in to use broker,
``parent_window_handle`` is required.
If your app is a GUI app,
you are recommended to also provide its window handle,
so that the sign in UI window will properly pop up on top of your window.
- If your app is a GUI app running on Windows or Mac system,
you are required to also provide its window handle,
so that the sign-in window will pop up on top of your window.
- If your app is a console app running on Windows or Mac system,
you can use a placeholder
``PublicClientApplication.CONSOLE_WINDOW_HANDLE``.
If your app is a console app (most Python scripts are console apps),
you can use a placeholder value ``msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE``.
Most Python scripts are console apps.
New in version 1.20.0.
Expand Down
50 changes: 30 additions & 20 deletions msal/broker.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""This module is an adaptor to the underlying broker.
It relies on PyMsalRuntime which is the package providing broker's functionality.
"""
from threading import Event
import json
import logging
import sys
import time
import uuid

Expand All @@ -23,7 +23,15 @@
except (ImportError, AttributeError): # AttributeError happens when a prior pymsalruntime uninstallation somehow leaved an empty folder behind
# PyMsalRuntime currently supports these Windows versions, listed in this MSFT internal link
# https://github.com/AzureAD/microsoft-authentication-library-for-cpp/pull/2406/files
raise ImportError('You need to install dependency by: pip install "msal[broker]>=1.20,<2"')
min_ver = {
"win32": "1.20",
"darwin": "1.31",
}.get(sys.platform)
if min_ver:
raise ImportError(
f'You must install dependency by: pip install "msal[broker]>={min_ver},<2"')
else: # Unsupported platform
raise ImportError("Dependency pymsalruntime unavailable on current platform")
# It could throw RuntimeError when running on ancient versions of Windows


Expand All @@ -35,14 +43,12 @@ class TokenTypeError(ValueError):
pass


class _CallbackData:
def __init__(self):
self.signal = Event()
self.result = None

def complete(self, result):
self.signal.set()
self.result = result
_redirect_uri_on_mac = "msauth.com.msauth.unsignedapp://auth" # Note:
# On Mac, the native Python has a team_id which links to bundle id
# com.apple.python3 however it won't give Python scripts better security.
# Besides, the homebrew-installed Pythons have no team_id
# so they have to use a generic placeholder anyway.
# The v-team chose to combine two situations into using same placeholder.


def _convert_error(error, client_id):
Expand All @@ -52,8 +58,9 @@ def _convert_error(error, client_id):
or "AADSTS7000218" in context # This "request body must contain ... client_secret" is just a symptom of current app has no WAM redirect_uri
):
raise RedirectUriError( # This would be seen by either the app developer or end user
"MsalRuntime won't work unless this one more redirect_uri is registered to current app: "
"ms-appx-web://Microsoft.AAD.BrokerPlugin/{}".format(client_id))
"MsalRuntime needs the current app to register these redirect_uri "
"(1) ms-appx-web://Microsoft.AAD.BrokerPlugin/{} (2) {}".format(
client_id, _redirect_uri_on_mac))
# OTOH, AAD would emit other errors when other error handling branch was hit first,
# so, the AADSTS50011/RedirectUriError is not guaranteed to happen.
return {
Expand All @@ -70,8 +77,8 @@ def _convert_error(error, client_id):


def _read_account_by_id(account_id, correlation_id):
"""Return an instance of MSALRuntimeAccount, or log error and return None"""
callback_data = _CallbackData()
"""Return an instance of MSALRuntimeError or MSALRuntimeAccount, or None"""
callback_data = pymsalruntime.CallbackData()
pymsalruntime.read_account_by_id(
account_id,
correlation_id,
Expand Down Expand Up @@ -142,7 +149,7 @@ def _signin_silently(
params.set_pop_params(
auth_scheme._http_method, auth_scheme._url.netloc, auth_scheme._url.path,
auth_scheme._nonce)
callback_data = _CallbackData()
callback_data = pymsalruntime.CallbackData()
for k, v in kwargs.items(): # This can be used to support domain_hint, max_age, etc.
if v is not None:
params.set_additional_parameter(k, str(v))
Expand All @@ -169,9 +176,12 @@ def _signin_interactively(
**kwargs):
params = pymsalruntime.MSALRuntimeAuthParameters(client_id, authority)
params.set_requested_scopes(scopes)
params.set_redirect_uri("https://login.microsoftonline.com/common/oauth2/nativeclient")
# This default redirect_uri value is not currently used by the broker
params.set_redirect_uri(
_redirect_uri_on_mac if sys.platform == "darwin" else
"https://login.microsoftonline.com/common/oauth2/nativeclient"
# This default redirect_uri value is not currently used by WAM
# but it is required by the MSAL.cpp to be set to a non-empty valid URI.
)
if prompt:
if prompt == "select_account":
if login_hint:
Expand All @@ -198,7 +208,7 @@ def _signin_interactively(
params.set_additional_parameter(k, str(v))
if claims:
params.set_decoded_claims(claims)
callback_data = _CallbackData()
callback_data = pymsalruntime.CallbackData(is_interactive=True)
pymsalruntime.signin_interactively(
parent_window_handle or pymsalruntime.get_console_window() or pymsalruntime.get_desktop_window(), # Since pymsalruntime 0.2+
params,
Expand Down Expand Up @@ -231,7 +241,7 @@ def _acquire_token_silently(
for k, v in kwargs.items(): # This can be used to support domain_hint, max_age, etc.
if v is not None:
params.set_additional_parameter(k, str(v))
callback_data = _CallbackData()
callback_data = pymsalruntime.CallbackData()
pymsalruntime.acquire_token_silently(
params,
correlation_id,
Expand All @@ -247,7 +257,7 @@ def _signout_silently(client_id, account_id, correlation_id=None):
account = _read_account_by_id(account_id, correlation_id)
if account is None:
return
callback_data = _CallbackData()
callback_data = pymsalruntime.CallbackData()
pymsalruntime.signout_silently( # New in PyMsalRuntime 0.7
client_id,
correlation_id,
Expand Down
Loading

0 comments on commit 86f448b

Please sign in to comment.