diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index de0c8efe70f..e9ccd9e4f06 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -148,6 +148,7 @@ def login(self, is_service_principal, tenant, scopes=None, + use_broker_sso=False, use_device_code=False, allow_no_subscriptions=False, use_cert_sn_issuer=None, @@ -176,7 +177,10 @@ def login(self, if use_device_code: user_identity = identity.login_with_device_code(scopes=scopes, claims_challenge=claims_challenge) else: - user_identity = identity.login_with_auth_code(scopes=scopes, claims_challenge=claims_challenge) + user_identity = identity.login_with_auth_code( + scopes=scopes, + claims_challenge=claims_challenge, + use_broker_sso=use_broker_sso) else: if not is_service_principal: user_identity = identity.login_with_username_password(username, password, scopes=scopes) diff --git a/src/azure-cli-core/azure/cli/core/auth/agentic_session.py b/src/azure-cli-core/azure/cli/core/auth/agentic_session.py index 33112322331..6ceed7cd3bf 100644 --- a/src/azure-cli-core/azure/cli/core/auth/agentic_session.py +++ b/src/azure-cli-core/azure/cli/core/auth/agentic_session.py @@ -26,6 +26,11 @@ COPILOT_AGENT_SESSION_ID = "COPILOT_AGENT_SESSION_ID" +def is_agentic_session(): + """Determine if we're in an agentic session based on the presence of COPILOT_AGENT_SESSION_ID.""" + return os.environ.get(COPILOT_AGENT_SESSION_ID) is not None + + def build_agentic_session_params(): """Read COPILOT_AGENT_SESSION_ID and build the agentic claims challenge. diff --git a/src/azure-cli-core/azure/cli/core/auth/identity.py b/src/azure-cli-core/azure/cli/core/auth/identity.py index 91629e89441..ec690b03ac7 100644 --- a/src/azure-cli-core/azure/cli/core/auth/identity.py +++ b/src/azure-cli-core/azure/cli/core/auth/identity.py @@ -145,7 +145,7 @@ def _service_principal_store(self): Identity._service_principal_store_instance = ServicePrincipalStore(store) return Identity._service_principal_store_instance - def login_with_auth_code(self, scopes, claims_challenge=None): + def login_with_auth_code(self, scopes, claims_challenge=None, use_broker_sso=False): # Emit a warning to inform that a browser is opened. # Only show the path part of the URL and hide the query string. @@ -161,15 +161,30 @@ def _prompt_launching_ui(ui=None, **_): from .util import read_response_templates success_template, error_template = read_response_templates() + from .agentic_session import is_agentic_session + is_agentic_session_value = is_agentic_session() + # Broker is available only on Windows for now + broker_sso_available = sys.platform.startswith('win') and self._enable_broker_on_windows + logger.debug("use_broker_sso: %s, is_agentic_session: %s, broker_sso_available: %s", + use_broker_sso, is_agentic_session_value, broker_sso_available) + use_broker_sso = (use_broker_sso or is_agentic_session_value) and broker_sso_available + prompt = 'none' if use_broker_sso else 'select_account' + # For AAD, use port 0 to let the system choose arbitrary unused ephemeral port to avoid port collision # on port 8400 from the old design. However, ADFS only allows port 8400. result = self._msal_app.acquire_token_interactive( - scopes, prompt='select_account', port=8400 if self._is_adfs else None, + scopes, + prompt=prompt, + port=8400 if self._is_adfs else None, success_template=success_template, error_template=error_template, parent_window_handle=self._msal_app.CONSOLE_WINDOW_HANDLE, on_before_launching_ui=_prompt_launching_ui, enable_msa_passthrough=True, claims_challenge=claims_challenge) - return check_result(result) + parsed = check_result(result) + if use_broker_sso: + # log parsed result in debug level + logger.debug("Result from broker SSO login: %s", json.dumps(parsed, indent=4)) + return parsed def login_with_device_code(self, scopes, claims_challenge=None): flow = self._msal_app.initiate_device_flow(scopes, claims_challenge=claims_challenge) diff --git a/src/azure-cli/azure/cli/command_modules/profile/__init__.py b/src/azure-cli/azure/cli/command_modules/profile/__init__.py index af25643541d..f19bab58c48 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/__init__.py +++ b/src/azure-cli/azure/cli/command_modules/profile/__init__.py @@ -58,6 +58,8 @@ def load_arguments(self, command): c.argument('claims_challenge', help="Base64-encoded claims challenge requested by a resource API in the " "WWW-Authenticate header.") + c.argument('use_broker_sso', action='store_true', + help="(Experimental) Enable broker SSO for login.") c.ignore('_subscription') # hide the global subscription parameter # Skip subscription discovery diff --git a/src/azure-cli/azure/cli/command_modules/profile/custom.py b/src/azure-cli/azure/cli/command_modules/profile/custom.py index 164a362a054..c02be79b17a 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/custom.py +++ b/src/azure-cli/azure/cli/command_modules/profile/custom.py @@ -134,7 +134,7 @@ def _select_and_set_active(profile, subscriptions): # pylint: disable=too-many-branches, too-many-locals def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_subscriptions=False, - claims_challenge=None, + claims_challenge=None, use_broker_sso=False, # Device code flow use_device_code=False, # Service principal @@ -169,6 +169,15 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_ logger.warning(USERNAME_PASSWORD_DEPRECATION_WARNING_AZURE_CLOUD) else: logger.warning(USERNAME_PASSWORD_DEPRECATION_WARNING_OTHER_CLOUD) + # broker_sso incompatible with workload identities + # broker_sso relies on the presence of a broker, which is only available on Windows (for now) + enable_broker_on_windows = cmd.cli_ctx.config.getboolean('core', 'enable_broker_on_windows', fallback=True) + broker_sso_available = sys.platform.startswith('win') and enable_broker_on_windows + if use_broker_sso and not broker_sso_available: + raise CLIError("usage error: '--use-broker-sso' is only supported on Windows with broker enabled") + if use_broker_sso and (service_principal or identity): + raise CLIError("usage error: '--use-broker-sso' is not applicable " + "to service principal or managed identity login") if claims_challenge: from azure.cli.core.util import b64decode @@ -219,6 +228,7 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_ service_principal, tenant, scopes=scopes, + use_broker_sso=use_broker_sso, use_device_code=use_device_code, allow_no_subscriptions=allow_no_subscriptions, use_cert_sn_issuer=use_cert_sn_issuer,