Skip to content

Commit a493a02

Browse files
committed
PoC: Interactive flow launches browser via Cloud Shell
Detect current signed-in user in Cloud Shell fixup! PoC: Interactive flow launches browser via Cloud Shell
1 parent 6eb3d79 commit a493a02

File tree

1 file changed

+64
-19
lines changed

1 file changed

+64
-19
lines changed

msal/application.py

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from .oauth2cli import Client, JwtAssertionCreator
1717
from .oauth2cli.oidc import decode_part
18+
from .oauth2cli.authcode import AuthCodeReceiver as _AuthCodeReceiver
1819
from .authority import Authority
1920
from .mex import send_request as mex_send_request
2021
from .wstrust_request import send_request as wst_send_request
@@ -1581,26 +1582,70 @@ def acquire_token_interactive(
15811582
self._client_capabilities, claims_challenge)
15821583
telemetry_context = self._build_telemetry_context(
15831584
self.ACQUIRE_TOKEN_INTERACTIVE)
1584-
response = _clean_up(self.client.obtain_token_by_browser(
1585-
scope=self._decorate_scope(scopes) if scopes else None,
1586-
extra_scope_to_consent=extra_scopes_to_consent,
1587-
redirect_uri="http://localhost:{port}".format(
1585+
if _is_running_in_cloud_shell():
1586+
redirect_uri = "https://azuread.github.io/microsoft-authentication-library-for-python/" # Need exact match, including the trailing slash.
1587+
receiver = _AuthCodeReceiver(
1588+
port=port or 0,
1589+
scheduled_actions=[(10, lambda: logger.warning(
1590+
"Still waiting for the sign-in result. "
1591+
"An experimental feature hint to app developer: "
1592+
"This app could still sign in when running inside Cloud Shell, "
1593+
"if its registration contains a Desktop app redirect_uri as: %s",
1594+
redirect_uri))],
1595+
)
1596+
resp = self.http_client.post( # Enable tunneling for the listening port
1597+
"http://localhost:8888/ports/{}/open".format(receiver.get_port()))
1598+
state = json.loads(resp.text)["url"] # Record tunnel url as state
1599+
if not login_hint: # Experimental: attempt to use current signed-in user
1600+
already_consented = "https://management.azure.com"
1601+
result = self._acquire_token_by_cloud_shell([already_consented])
1602+
if "access_token" in result:
1603+
cloud_shell_claims = json.loads(decode_part(
1604+
result["access_token"].split(".")[1]))
1605+
login_hint = (
1606+
# https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens
1607+
cloud_shell_claims.get("preferred_username") # In v2.0 AT
1608+
or cloud_shell_claims.get("unique_name") # In v1.0 AT
1609+
or cloud_shell_claims.get("email") # Undocumented but observable from a V1.0 AT
1610+
).split("#")[-1] # Deal with "live.com#[email protected]"
1611+
else: # Non-CloudShell scenario
1612+
receiver = state = None
1613+
redirect_uri = "http://localhost:{port}".format(
15881614
# Hardcode the host, for now. AAD portal rejects 127.0.0.1 anyway
1589-
port=port or 0),
1590-
prompt=prompt,
1591-
login_hint=login_hint,
1592-
max_age=max_age,
1593-
timeout=timeout,
1594-
auth_params={
1595-
"claims": claims,
1596-
"domain_hint": domain_hint,
1597-
},
1598-
data=dict(kwargs.pop("data", {}), claims=claims),
1599-
headers=telemetry_context.generate_headers(),
1600-
browser_name=_preferred_browser(),
1601-
**kwargs))
1602-
telemetry_context.update_telemetry(response)
1603-
return response
1615+
port=port or 0)
1616+
try:
1617+
response = _clean_up(self.client.obtain_token_by_browser(
1618+
scope=self._decorate_scope(scopes) if scopes else None,
1619+
extra_scope_to_consent=extra_scopes_to_consent,
1620+
redirect_uri=redirect_uri,
1621+
prompt=prompt,
1622+
login_hint=login_hint,
1623+
max_age=max_age,
1624+
timeout=timeout,
1625+
success_template=kwargs.pop("success_template", """<html><body>
1626+
<script>window.close()</script> <!--
1627+
Window can be closed if the tab was manually opened by clicking via Cloud Shell;
1628+
otherwise it will result in harmless error in browser console. -->
1629+
<h3>Authenticaiton completed</h3>
1630+
You can close this window now.
1631+
</body></html>"""),
1632+
auth_params={
1633+
"claims": claims,
1634+
"domain_hint": domain_hint,
1635+
"state": state,
1636+
},
1637+
data=dict(kwargs.pop("data", {}), claims=claims),
1638+
headers=telemetry_context.generate_headers(),
1639+
browser_name=_preferred_browser(),
1640+
auth_uri_callback=lambda uri: logger.info(
1641+
"Unable to open browser tab. Please visit this URL: %s", uri),
1642+
auth_code_receiver=receiver,
1643+
**kwargs))
1644+
telemetry_context.update_telemetry(response)
1645+
return response
1646+
finally:
1647+
if receiver:
1648+
receiver.close()
16041649

16051650
def initiate_device_flow(self, scopes=None, **kwargs):
16061651
"""Initiate a Device Flow instance,

0 commit comments

Comments
 (0)