Skip to content

Commit 9715f07

Browse files
fix(kernel): forward redirect_ports list to kernel U2M (PECOBLR-4144)
databricks-sql-kernel #257 landed: the pyo3 Session now takes redirect_ports (a list) and no longer accepts the single redirect_port kwarg. Update the kernel auth bridge to emit redirect_ports for databricks-oauth U2M, forwarding the databricks-sql-python app's FULL registered port list (PYSQL_OAUTH_REDIRECT_PORT_RANGE, 8020-8024) so the kernel binds the first free port — busy-port fallback, matching the Thrift DatabricksOAuthProvider. A custom client_id + explicit port pins that single port ([port]). Bump KERNEL_REV to the merged #257 commit (45a0d6a) so kernel-e2e builds against the kernel that exposes redirect_ports. Tests updated. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 <e.wang@databricks.com>
1 parent a03205a commit 9715f07

3 files changed

Lines changed: 32 additions & 30 deletions

File tree

KERNEL_REV

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
eff8950428f4e6cc9975c663ec919f334962f7d0
1+
45a0d6ae1de2f203220913ba96c994ebb2d7aae4

src/databricks/sql/backend/kernel/auth_bridge.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
provider.
1818
- **OAuth U2M** — for ``auth_type`` ``databricks-oauth`` (the browser
1919
authorization-code flow), the connector's ``databricks-sql-python``
20-
app bundle (``client_id`` + ``redirect_port``, with the optional
20+
app bundle (``client_id`` + ``redirect_ports`` list, with the optional
2121
``oauth_client_id`` / ``oauth_redirect_port`` overriding it) is
2222
forwarded to the kernel's ``auth_type='oauth-u2m'`` and the kernel
2323
runs the browser flow itself. ``azure-oauth`` (Azure AD) is **not yet
@@ -162,7 +162,7 @@ def kernel_auth_kwargs(
162162
``AccessTokenAuthProvider`` → extract the bearer token.
163163
3. **OAuth U2M** — ``auth_type`` is ``databricks-oauth`` → forward the
164164
connector's coupled ``databricks-sql-python`` bundle (``client_id``
165-
+ ``redirect_port``, defaulting scopes to ``PYSQL_OAUTH_SCOPES``
165+
+ ``redirect_ports`` list, defaulting scopes to ``PYSQL_OAUTH_SCOPES``
166166
when the caller supplies none) to the kernel's ``oauth-u2m``, so a
167167
bare U2M connection authenticates as ``databricks-sql-python`` —
168168
forwarding the connector's own OAuth app rather than the kernel's
@@ -255,15 +255,15 @@ def kernel_auth_kwargs(
255255
# Only databricks-oauth reaches here (azure-oauth rejected up front).
256256
# Forward the connector's own databricks-sql-python bundle instead of
257257
# the kernel's databricks-sql-connector default, for parity with the
258-
# Thrift path. client_id + redirect_port are coupled per app (each
259-
# registers its own redirect URI): a caller port only overrides the
258+
# Thrift path. client_id + redirect ports are coupled per app (each
259+
# registers its own redirect URIs): a caller port only overrides the
260260
# default when an explicit client_id is also supplied. A caller may
261261
# override oauth_scopes; absent one we forward PYSQL_OAUTH_SCOPES as
262-
# the default. NB: the kernel's redirect_port is a single int, so
263-
# unlike the Thrift path (which hands DatabricksOAuthProvider the full
264-
# PYSQL_OAUTH_REDIRECT_PORT_RANGE and retries the next port when one is
265-
# bound) this path forwards only one port with no fallback. A caller
266-
# hitting a port collision must pass oauth_redirect_port explicitly.
262+
# the default. We forward the FULL PYSQL_OAUTH_REDIRECT_PORT_RANGE as
263+
# ``redirect_ports`` so the kernel binds the first free port (busy-port
264+
# fallback), mirroring the Thrift DatabricksOAuthProvider which retries
265+
# the next port when one is bound. A caller overriding client_id
266+
# supplies its own single registered port.
267267
if auth_type == "databricks-oauth":
268268
redirect_port = opts.get("oauth_redirect_port")
269269
# Honor a caller-supplied oauth_scopes (normalized to a list of
@@ -272,10 +272,10 @@ def kernel_auth_kwargs(
272272
kwargs = {
273273
"auth_type": "oauth-u2m",
274274
"client_id": client_id or PYSQL_OAUTH_CLIENT_ID,
275-
"redirect_port": (
276-
_coerce_redirect_port(redirect_port)
275+
"redirect_ports": (
276+
[_coerce_redirect_port(redirect_port)]
277277
if client_id and redirect_port is not None
278-
else PYSQL_OAUTH_REDIRECT_PORT_RANGE[0]
278+
else list(PYSQL_OAUTH_REDIRECT_PORT_RANGE)
279279
),
280280
"oauth_scopes": scopes if scopes is not None else list(PYSQL_OAUTH_SCOPES),
281281
}

tests/unit/test_kernel_auth_bridge.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,9 @@ class TestKernelOAuthU2M:
251251
The kernel core default U2M app is ``databricks-sql-connector`` /
252252
``sql offline_access`` / port 8030 (see PECOBLR-4039). The Python
253253
connector is an OVERRIDE: on the kernel path it forwards its OWN
254-
coupled ``client_id`` + ``redirect_port`` bundle so it authenticates
255-
as ``databricks-sql-python`` rather than the kernel default. A caller
254+
coupled ``client_id`` + ``redirect_ports`` bundle (the full registered
255+
port list, for busy-port fallback) so it authenticates as
256+
``databricks-sql-python`` rather than the kernel default. A caller
256257
may override ``oauth_scopes``; absent one, ``PYSQL_OAUTH_SCOPES`` is
257258
forwarded as the default.
258259
@@ -271,7 +272,8 @@ def test_bare_databricks_oauth_forwards_full_python_bundle(self):
271272
assert kwargs == {
272273
"auth_type": "oauth-u2m",
273274
"client_id": PYSQL_OAUTH_CLIENT_ID,
274-
"redirect_port": PYSQL_OAUTH_REDIRECT_PORT_RANGE[0],
275+
# Full registered port list → the kernel binds the first free one.
276+
"redirect_ports": list(PYSQL_OAUTH_REDIRECT_PORT_RANGE),
275277
"oauth_scopes": list(PYSQL_OAUTH_SCOPES),
276278
}
277279

@@ -294,8 +296,9 @@ def test_azure_oauth_not_supported(self, opts):
294296
kernel_auth_kwargs(_FakeOAuthProvider(), opts)
295297

296298
def test_u2m_custom_client_id_port_and_scopes_honored(self):
297-
# A caller may override the coupled client_id + redirect_port and
298-
# the oauth_scopes. All three are forwarded as supplied.
299+
# A caller may override the coupled client_id + redirect port and the
300+
# oauth_scopes. The single custom port is forwarded as a one-element
301+
# redirect_ports list; all three are forwarded as supplied.
299302
kwargs = kernel_auth_kwargs(
300303
_FakeOAuthProvider(),
301304
{
@@ -308,14 +311,14 @@ def test_u2m_custom_client_id_port_and_scopes_honored(self):
308311
assert kwargs == {
309312
"auth_type": "oauth-u2m",
310313
"client_id": "custom-client",
311-
"redirect_port": 9999,
314+
"redirect_ports": [9999],
312315
"oauth_scopes": ["custom-scope", "offline_access"],
313316
}
314317

315318
def test_u2m_custom_client_id_only_falls_back_to_connector_defaults(self):
316319
# A custom client_id without explicit scopes/port fills the
317320
# remaining two from the connector defaults — a custom client_id
318-
# still uses PYSQL_OAUTH_SCOPES and the default redirect-port range.
321+
# still uses PYSQL_OAUTH_SCOPES and the default redirect-port list.
319322
kwargs = kernel_auth_kwargs(
320323
_FakeOAuthProvider(),
321324
{
@@ -326,7 +329,7 @@ def test_u2m_custom_client_id_only_falls_back_to_connector_defaults(self):
326329
assert kwargs == {
327330
"auth_type": "oauth-u2m",
328331
"client_id": "custom-client",
329-
"redirect_port": PYSQL_OAUTH_REDIRECT_PORT_RANGE[0],
332+
"redirect_ports": list(PYSQL_OAUTH_REDIRECT_PORT_RANGE),
330333
"oauth_scopes": list(PYSQL_OAUTH_SCOPES),
331334
}
332335

@@ -343,8 +346,8 @@ def test_u2m_redirect_port_coerced_to_int(self):
343346
"oauth_redirect_port": "8021",
344347
},
345348
)
346-
assert kwargs["redirect_port"] == 8021
347-
assert isinstance(kwargs["redirect_port"], int)
349+
assert kwargs["redirect_ports"] == [8021]
350+
assert isinstance(kwargs["redirect_ports"][0], int)
348351

349352
def test_u2m_redirect_port_non_numeric_raises_programming_error(self):
350353
# A garbled oauth_redirect_port is a caller error of the same class
@@ -362,18 +365,17 @@ def test_u2m_redirect_port_non_numeric_raises_programming_error(self):
362365
)
363366

364367
def test_u2m_redirect_port_ignored_without_client_id(self):
365-
# A bare oauth_redirect_port (no explicit client_id) must NOT be
366-
# forwarded: it would be paired with the default databricks-sql-python
367-
# app, whose registered redirect URIs only cover the default port
368-
# range, so an arbitrary port would resolve to an unregistered URI
369-
# and fail the U2M flow. This mirrors the Thrift path's coupling,
370-
# where oauth_redirect_port_range is only overridden when both
368+
# A bare oauth_redirect_port (no explicit client_id) must NOT replace
369+
# the default list: it would be paired with the default
370+
# databricks-sql-python app, so we keep forwarding that app's full
371+
# registered port list. This mirrors the Thrift path's coupling, where
372+
# oauth_redirect_port_range is only overridden when both
371373
# oauth_client_id and oauth_redirect_port are supplied.
372374
kwargs = kernel_auth_kwargs(
373375
_FakeOAuthProvider(),
374376
{"auth_type": "databricks-oauth", "oauth_redirect_port": 9999},
375377
)
376-
assert kwargs["redirect_port"] == PYSQL_OAUTH_REDIRECT_PORT_RANGE[0]
378+
assert kwargs["redirect_ports"] == list(PYSQL_OAUTH_REDIRECT_PORT_RANGE)
377379

378380
def test_u2m_honors_custom_scopes(self):
379381
# A caller-supplied oauth_scopes is forwarded to the kernel, even

0 commit comments

Comments
 (0)