Skip to content

Commit b7bd4f8

Browse files
authored
Merge branch 'main' into nicholas-lee_data/SC-179551
2 parents 9aab55a + d3b85cb commit b7bd4f8

File tree

9 files changed

+480
-158
lines changed

9 files changed

+480
-158
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Version changelog
22

3+
## [Release] Release v0.36.0
4+
5+
### Breaking Changes
6+
* `external_browser` now uses the `databricks-cli` app instead of the third-party "6128a518-99a9-425b-8333-4cc94f04cacd" application when performing the U2M login flow for Azure workspaces when a client ID is not otherwise specified. This matches the AWS behavior.
7+
* The signatures of several OAuth-related constructors have changed to support U2M OAuth with Azure Entra ID application registrations. See https://github.com/databricks/databricks-sdk-py/blob/main/examples/flask_app_with_oauth.py for examples of how to use these classes.
8+
* `OAuthClient()`: renamed to `OAuthClient.from_host()`
9+
* `SessionCredentials()` and `SessionCredentials.from_dict()`: now accepts `token_endpoint`, `client_id`, `client_secret`, and `refresh_url` as parameters, rather than accepting the `OAuthClient`.
10+
* `TokenCache()`: now accepts `host`, `token_endpoint`, `client_id`, `client_secret`, and `refresh_url` as parameters, rather than accepting the `OAuthClient`.
11+
12+
### Bug Fixes
13+
14+
* Decouple OAuth functionality from `Config` ([#784](https://github.com/databricks/databricks-sdk-py/pull/784)).
15+
16+
17+
### Release
18+
19+
* Release v0.35.0 ([#793](https://github.com/databricks/databricks-sdk-py/pull/793)).
20+
21+
22+
323
## [Release] Release v0.35.0
424

525
### New Features and Improvements

databricks/sdk/_base_client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import urllib.parse
23
from datetime import timedelta
34
from types import TracebackType
45
from typing import (Any, BinaryIO, Callable, Dict, Iterable, Iterator, List,
@@ -17,6 +18,25 @@
1718
logger = logging.getLogger('databricks.sdk')
1819

1920

21+
def _fix_host_if_needed(host: Optional[str]) -> Optional[str]:
22+
if not host:
23+
return host
24+
25+
# Add a default scheme if it's missing
26+
if '://' not in host:
27+
host = 'https://' + host
28+
29+
o = urllib.parse.urlparse(host)
30+
# remove trailing slash
31+
path = o.path.rstrip('/')
32+
# remove port if 443
33+
netloc = o.netloc
34+
if o.port == 443:
35+
netloc = netloc.split(':')[0]
36+
37+
return urllib.parse.urlunparse((o.scheme, netloc, path, o.params, o.query, o.fragment))
38+
39+
2040
class _BaseClient:
2141

2242
def __init__(self,

databricks/sdk/config.py

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
import requests
1111

1212
from . import useragent
13+
from ._base_client import _fix_host_if_needed
1314
from .clock import Clock, RealClock
1415
from .credentials_provider import CredentialsStrategy, DefaultCredentials
1516
from .environments import (ALL_ENVS, AzureEnvironment, Cloud,
1617
DatabricksEnvironment, get_environment_for_hostname)
17-
from .oauth import OidcEndpoints, Token
18+
from .oauth import (OidcEndpoints, Token, get_account_endpoints,
19+
get_azure_entra_id_workspace_endpoints,
20+
get_workspace_endpoints)
1821

1922
logger = logging.getLogger('databricks.sdk')
2023

@@ -254,24 +257,10 @@ def oidc_endpoints(self) -> Optional[OidcEndpoints]:
254257
if not self.host:
255258
return None
256259
if self.is_azure and self.azure_client_id:
257-
# Retrieve authorize endpoint to retrieve token endpoint after
258-
res = requests.get(f'{self.host}/oidc/oauth2/v2.0/authorize', allow_redirects=False)
259-
real_auth_url = res.headers.get('location')
260-
if not real_auth_url:
261-
return None
262-
return OidcEndpoints(authorization_endpoint=real_auth_url,
263-
token_endpoint=real_auth_url.replace('/authorize', '/token'))
260+
return get_azure_entra_id_workspace_endpoints(self.host)
264261
if self.is_account_client and self.account_id:
265-
prefix = f'{self.host}/oidc/accounts/{self.account_id}'
266-
return OidcEndpoints(authorization_endpoint=f'{prefix}/v1/authorize',
267-
token_endpoint=f'{prefix}/v1/token')
268-
oidc = f'{self.host}/oidc/.well-known/oauth-authorization-server'
269-
res = requests.get(oidc)
270-
if res.status_code != 200:
271-
return None
272-
auth_metadata = res.json()
273-
return OidcEndpoints(authorization_endpoint=auth_metadata.get('authorization_endpoint'),
274-
token_endpoint=auth_metadata.get('token_endpoint'))
262+
return get_account_endpoints(self.host, self.account_id)
263+
return get_workspace_endpoints(self.host)
275264

276265
def debug_string(self) -> str:
277266
""" Returns log-friendly representation of configured attributes """
@@ -346,22 +335,9 @@ def attributes(cls) -> Iterable[ConfigAttribute]:
346335
return cls._attributes
347336

348337
def _fix_host_if_needed(self):
349-
if not self.host:
350-
return
351-
352-
# Add a default scheme if it's missing
353-
if '://' not in self.host:
354-
self.host = 'https://' + self.host
355-
356-
o = urllib.parse.urlparse(self.host)
357-
# remove trailing slash
358-
path = o.path.rstrip('/')
359-
# remove port if 443
360-
netloc = o.netloc
361-
if o.port == 443:
362-
netloc = netloc.split(':')[0]
363-
364-
self.host = urllib.parse.urlunparse((o.scheme, netloc, path, o.params, o.query, o.fragment))
338+
updated_host = _fix_host_if_needed(self.host)
339+
if updated_host:
340+
self.host = updated_host
365341

366342
def load_azure_tenant_id(self):
367343
"""[Internal] Load the Azure tenant ID from the Azure Databricks login page.

databricks/sdk/credentials_provider.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -187,30 +187,35 @@ def token() -> Token:
187187
def external_browser(cfg: 'Config') -> Optional[CredentialsProvider]:
188188
if cfg.auth_type != 'external-browser':
189189
return None
190+
client_id, client_secret = None, None
190191
if cfg.client_id:
191192
client_id = cfg.client_id
192-
elif cfg.is_aws:
193+
client_secret = cfg.client_secret
194+
elif cfg.azure_client_id:
195+
client_id = cfg.azure_client
196+
client_secret = cfg.azure_client_secret
197+
198+
if not client_id:
193199
client_id = 'databricks-cli'
194-
elif cfg.is_azure:
195-
# Use Azure AD app for cases when Azure CLI is not available on the machine.
196-
# App has to be registered as Single-page multi-tenant to support PKCE
197-
# TODO: temporary app ID, change it later.
198-
client_id = '6128a518-99a9-425b-8333-4cc94f04cacd'
199-
else:
200-
raise ValueError(f'local browser SSO is not supported')
201-
oauth_client = OAuthClient(host=cfg.host,
202-
client_id=client_id,
203-
redirect_url='http://localhost:8020',
204-
client_secret=cfg.client_secret)
205200

206201
# Load cached credentials from disk if they exist.
207202
# Note that these are local to the Python SDK and not reused by other SDKs.
208-
token_cache = TokenCache(oauth_client)
203+
oidc_endpoints = cfg.oidc_endpoints
204+
redirect_url = 'http://localhost:8020'
205+
token_cache = TokenCache(host=cfg.host,
206+
oidc_endpoints=oidc_endpoints,
207+
client_id=client_id,
208+
client_secret=client_secret,
209+
redirect_url=redirect_url)
209210
credentials = token_cache.load()
210211
if credentials:
211212
# Force a refresh in case the loaded credentials are expired.
212213
credentials.token()
213214
else:
215+
oauth_client = OAuthClient(oidc_endpoints=oidc_endpoints,
216+
client_id=client_id,
217+
redirect_url=redirect_url,
218+
client_secret=client_secret)
214219
consent = oauth_client.initiate_consent()
215220
if not consent:
216221
return None

0 commit comments

Comments
 (0)