|
15 | 15 |
|
16 | 16 | from .oauth2cli import Client, JwtAssertionCreator |
17 | 17 | from .oauth2cli.oidc import decode_part |
| 18 | +from .oauth2cli.authcode import AuthCodeReceiver as _AuthCodeReceiver |
18 | 19 | from .authority import Authority |
19 | 20 | from .mex import send_request as mex_send_request |
20 | 21 | from .wstrust_request import send_request as wst_send_request |
@@ -1581,26 +1582,70 @@ def acquire_token_interactive( |
1581 | 1582 | self._client_capabilities, claims_challenge) |
1582 | 1583 | telemetry_context = self._build_telemetry_context( |
1583 | 1584 | 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( |
1588 | 1614 | # 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() |
1604 | 1649 |
|
1605 | 1650 | def initiate_device_flow(self, scopes=None, **kwargs): |
1606 | 1651 | """Initiate a Device Flow instance, |
|
0 commit comments