CXH-2166: revert PAT auth and workspace filtering (revert #54) - #65
Conversation
Reverts the PAT auth path added in #54, removing the implementation rather than leaving it dormant, and updates the docs to describe what the connector actually does. PAT auth was already unreachable: #63 commented out the workspace-token field group and rejected the auth method in ValidateConfig, leaving the implementation in place behind docs that described it as temporarily unavailable and expected to return. OAuth is now the connector's only authentication method, and the docs say so without qualification. Removed: - TokenAuth/NewTokenAuth and Client.IsTokenAuth - workspace-tokens field, the FieldsDependentOn constraint, and ValidateConfig (the workspaces/exclude-workspaces exclusion is a schema constraint) - the field groups entirely, along with both group constants: groups exist to let the setup UI offer a choice of auth method, and with OAuth as the only method there is no choice to present. This restores the pre-#54 shape, which had no groups. The three OAuth fields are required at the field level, so required-ness is unchanged. - the token branch in prepareClientAuth; Validate's account-API fallback branches and account-unreachable warning, now dead because isAccAPIAvailable is unconditionally true - isGroupNotFoundError and groupGrantParent, reachable only via workspace-parented groups, which existed only under token auth Kept: --workspaces (scopes OAuth syncs), --databricks-exclude-workspaces, and the grant-parenting fixes in account.go and service-principals.go, which are correct under OAuth. roles.go inlines groupGrantParent's account branch. Docs (README, connector.mdx, docs-info.md) drop the PAT method and its "temporarily unavailable" framing; the README CLI dump is regenerated from the built binary. conf.gen.go and config_schema.json regenerated. Breaking change for self-hosted deployments still configured with workspace tokens: they must move to OAuth2 or pin a previous version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
334689b to
f85c196
Compare
| isWSAPIAvailable := false | ||
| for _, workspace := range workspaceNames { | ||
| _, _, err := d.client.ListRoles(ctx, workspace, "", "") | ||
| if err != nil && !isAccAPIAvailable { | ||
| return nil, fmt.Errorf("databricks-connector: failed to validate credentials for workspace %s: %w", workspace, err) | ||
| // Not fatal: the account API already validated, and a workspace the service | ||
| // principal can't reach is skipped during sync rather than failing it. | ||
| if _, _, err := d.client.ListRoles(ctx, workspace, "", ""); err != nil { | ||
| ctxzap.Extract(ctx).Debug("databricks-connector: workspace validation probe failed", | ||
| zap.String("workspace", workspace), | ||
| zap.Error(err), | ||
| ) | ||
| } | ||
|
|
||
| isWSAPIAvailable = true | ||
| } |
There was a problem hiding this comment.
🟡 Suggestion: this loop no longer influences anything. isWSAPIAvailable is set to true unconditionally inside the body regardless of the probe result, so it reduces to len(workspaceNames) > 0, and Client.IsWorkspaceAPIAvailable() (the only consumer of that bit) has no callers anywhere in the repo. The net effect is one ListRoles call per workspace on every sync start whose outcome is only Debug-logged — on a large account that's N sequential API calls for no signal. Either drop the loop, or make the outcome meaningful and log the failure at Warn (per the repo's L1 log-level rule): a workspace the service principal can't reach here will hard-fail later in roles.go, so a debug line invisible at the default info level is the quiet-degradation shape the criteria warn about.
There was a problem hiding this comment.
Dropped the loop in db1a1e3a. You're right that it reduced to len(workspaces) > 0 — isWSAPIAvailable = true sat outside the error branch — and IsWorkspaceAPIAvailable() had no callers anywhere in the repo, so the whole bit was write-only. Rather than raise the log level I removed the probe entirely: a workspace the service principal can't reach is already skipped during sync, so the N sequential calls bought nothing. ListWorkspaces stays, since every sync depends on it.
| "and identities are parented under their workspace instead of the account", | ||
| ) | ||
| } | ||
| d.client.UpdateAvailability(true, isWSAPIAvailable) |
There was a problem hiding this comment.
🟡 Suggestion: hardcoding true here is correct now that OAuth is the only auth, but it leaves Client.isAccAPIAvailable as a field that defaults to false and only flips when Validate runs. Four branches still gate real sync output on it — account.go:52 (account child resource types), account.go:94/account.go:111 (account entitlements/grants), and workspaces.go:147/workspaces.go:169 (workspace-member entitlements/grants) — so they are now dead-but-load-bearing on Validate having executed first. Consider initializing isAccAPIAvailable: true in NewClient and deleting those branches (and the now-unused IsWorkspaceAPIAvailable), so the invariant isn't carried by call ordering.
There was a problem hiding this comment.
Done in db1a1e3a, along the lines you suggested: isAccAPIAvailable is now set in NewClient, so the five branches gating on it no longer depend on Validate having run first. UpdateAvailability, IsWorkspaceAPIAvailable and isWSAPIAvailable are gone. I left the gating branches themselves in place — deleting them touches sync-output paths, which is more than this revert should carry; happy to do it as a follow-up.
Connector PR Review: CXH-2166: revert PAT auth and workspace filtering (revert #54)Blocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0 Review SummaryThe new commits finish collapsing Security IssuesNone found. These commits only remove code (the workspace probe loop and two availability accessors); no credential handling, logging, or auth check is added or weakened. Correctness IssuesNone found. Verified there are no remaining references to the deleted Suggestions
Prompt for AI agentsReview state marker omitted: this run's shell sandbox rejects the JSON marker text, so the next review will fall back to a full-diff pass. Head SHA reviewed: |
Full revert of #54. CXH-2109 records both "PAT and username/password auth are not implemented" and "workspace filtering is not implemented" as the connector's actual, expected behaviour; the fix for both is to correct the documentation, not to build the features. #54 built them instead, so this removes them and updates the docs to match what the connector does. PAT auth was already unreachable: #63 commented out the workspace-token field group and rejected the auth method in ValidateConfig, leaving the implementation behind docs that called it temporarily unavailable and expected to return. OAuth2 is now the connector's only auth method, and the docs say so without qualification. Reverted to their pre-#54 state, byte for byte: pkg/config/config.go (workspaces + workspace-tokens fields, the field groups, the constraints, ValidateConfig) pkg/databricks/auth.go (TokenAuth) pkg/connector/helpers.go (groupGrantParent, isGroupNotFoundError) pkg/connector/workspaces.go (workspace allowlist filtering) pkg/connector/groups.go pkg/connector/roles.go pkg/connector/account.go pkg/connector/service-principals.go pkg/databricks/client.go drops IsTokenAuth and IsWorkspaceNameExcluded, both added by #54, and keeps everything added after it. Preserved in full, since they postdate #54 and belong to other tickets: CXH-2350's fatal account-API probe, wrapTransportAuthError and their tests; the Azure/GCP account-host fix; PR #55's 403 remedy; and the databricks-exclude-workspaces flag, which predates #54. Docs (README, connector.mdx, docs-info.md) drop both the PAT method and the workspace allowlist. The README CLI dump, conf.gen.go and config_schema.json are regenerated from the built binary. Breaking change for self-hosted deployments using workspace tokens or --workspaces: workspace tokens must move to OAuth2, and scope narrowing must use --databricks-exclude-workspaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The otherwise-complete revert of #54 reinstated a grant-parenting bug that #54 had incidentally fixed, so carve that one hunk out of the revert. accountBuilder.Grants passed resource.ParentResourceId into groupGrantExpansion. There resource is the account itself, whose ParentResourceId is nil, so groupResourceId returned "group/<id>" while groups actually sync as "account/<accountId>/group/<id>" — the expansion named a resource that no sync emits. Unlike the rest of #54 this is not PAT or workspace-filtering work: it is wrong under OAuth too, which is the only auth method left. Reverting it would regress against main for no benefit. servicePrincipalBuilder.Grants keeps the reverted form: there resource is a service principal, which is genuinely parented, so ParentResourceId already names the right parent under OAuth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Carves the second grant-parenting hunk out of the revert, alongside the account one. servicePrincipalBuilder.Grants passed resource.ParentResourceId into groupResourceId. Unlike the account case this is non-nil, so it is not broken under OAuth today, but it reads the parent from a different source than groupBuilder does when it syncs the group. Naming the parent from the service principal's own profile, as the rest of this method already does for workspaceId, keeps the expansion and the synced group derived from one source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| l.Debug("using workspace token auth", zap.String("account-id", cfg.AccountId)) | ||
| return databricks.NewTokenAuth(cfg.Workspaces, cfg.WorkspaceTokens) | ||
| } | ||
| func prepareClientAuth(_ context.Context, cfg *config.Databricks, l *zap.Logger) databricks.Auth { |
There was a problem hiding this comment.
🟡 Suggestion: prepareClientAuth now has two dead parameters — the newly added _ context.Context, and l *zap.Logger, whose only use (l.Debug("using oauth", ...)) was deleted in this commit. NewConnector also renamed _ *cli.ConnectorOpts to opts without using it. Dropping ctx/l and restoring _ for opts would keep the signatures honest (connector mixin R2). Confidence: high; cosmetic only, revive's unused-parameter rule isn't enabled so lint won't catch it.
There was a problem hiding this comment.
Fixed in db1a1e3a — prepareClientAuth(cfg *config.Databricks), and opts is back to _. The ctxzap/zap imports went with it, since the redundant NewConnector Warn was removed in the same commit.
| @@ -149,12 +149,6 @@ func (g *groupBuilder) Entitlements(ctx context.Context, resource *v2.Resource, | |||
| // get all assignable roles for this specific group resource | |||
There was a problem hiding this comment.
[medium] This skip guard (and its twin at the Grants rule-sets call below) isn't PAT/workspace-filtering-specific, even though it's being removed along with that work. workspaceId here comes from resource.ParentResourceId.Resource — still populated today, under OAuth-only auth, whenever a group is parented to a workspace rather than the account (see groupBuilder.Entitlements/Grants in this same file). The removed comment described a real, distinct failure mode: "an orphaned or stale workspace SCIM group" causing the rule-sets/roles API to 400 with a group-not-found message. Without this guard, one stale workspace group now hard-fails the entire Entitlements/Grants call for that group (fmt.Errorf(...) propagates) instead of being warned and skipped.
This looks like the same category of thing as the two grant-parent fixes this PR already keeps independently of the revert (account.go/service-principals.go) — a genuine improvement that happened to ship inside #54, not itself PAT-or-workspace-filtering work. Worth considering keeping it (and its helpers.go/helpers_test.go counterpart) the same way, in its own commit, rather than reverting it along with everything else.
Codex verification (independent adversarial pass): CONFIRMED as an additional major — "the revert drops the isGroupNotFoundError skip path and now hard-fails on those API responses... turning that back into fatal errors can make one stale/orphaned workspace group abort an otherwise valid sync." I independently confirmed workspaceId is still reachable and non-empty under OAuth for workspace-parented groups before including this.
There was a problem hiding this comment.
Thanks for digging into this one — I went looking to keep it, but I don't think the reachability premise holds once the rest of the revert lands.
The claim is that workspaceId is "still populated today, under OAuth-only auth, whenever a group is parented to a workspace." The second half is the part that stops being true on this branch: after the revert, nothing ever parents a group to a workspace.
The chain:
groupBuilder.List(pkg/connector/groups.go:76) is only ever called with parents whose resource declaresgroupas a child resource type.- On
mainthere are two workspace builders.minimalWorkspaceResourceannotatesuser/group/service_principal/roleas children — and its own doc comment says why it exists: "for token auth where the Account API (and its numeric workspace IDs) is unreachable… Users, groups and service principals hang off the workspace here instead of the account." That function is CXH-2166: implement PAT (workspace token) authentication #54 code, and this PR reverts it. - What's left is
workspaceResource(pkg/connector/workspaces.go:46on this branch), which annotates onlyroleResourceTypeas a child. I checked it back through history:b38a0beb^1had onlyroletoo, so this is the pre-CXH-2166: implement PAT (workspace token) authentication #54 shape being restored, not something the revert invented. - So post-revert the only parent that lists groups is the account (
pkg/connector/account.go:53),parentResourceID.ResourceTypeis alwaysaccount, and bothworkspaceIdassignments —groups.go:130inEntitlementsand theisWorkspaceGroupbranch inGrants— always evaluate to"".
With workspaceId empty, the rule-sets call goes to the account plane, where the "orphaned or stale workspace SCIM group" 400 the guard was written for can't arise. So unlike the two grant-parent hunks — which fix behaviour that's live under OAuth today — this one has no reachable path left to protect, and keeping it would restore a helper plus its tests for a branch that can't execute.
The distinction I'm drawing: the account.go hunk fixes a grant expansion that names a resource no sync emits, right now, under OAuth. This guard only bites when groups sync under a workspace, which is exactly the token-auth topology being removed. If workspace-parented groups ever come back, the guard should come back with them.
Happy to be wrong if you're seeing a path to a workspace-parented group that I've missed — that'd change the answer.
With OAuth as the only auth method, the account plane is either reachable or Validate hard-fails, so the account/workspace availability split no longer carries information. - Validate's per-workspace ListRoles loop set isWSAPIAvailable unconditionally inside its own body, reducing to len(workspaces) > 0, and only Debug-logged failures. It cost one sequential API call per workspace on every sync start for no signal. Workspace enumeration itself is kept: every sync depends on it. - IsWorkspaceAPIAvailable had no callers anywhere in the repo; it and isWSAPIAvailable go with the loop, and UpdateAvailability with them. - isAccAPIAvailable is now set in NewClient instead of only flipping when Validate runs, so the five branches still gating sync output on it no longer depend on call ordering. Also drops prepareClientAuth's two dead parameters (the unused context.Context, and the *zap.Logger whose only use was deleted with the "using oauth" Debug), restores the blank identifier for NewConnector's unused *cli.ConnectorOpts, and removes the redundant Warn before returning an already-wrapped error from NewConnector, which the revert had pulled back in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
isAccAPIAvailable is no longer set by Validate, and the workspace check no longer runs when the account probe fails: Validate returns immediately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| // OAuth is the only auth method, and Validate hard-fails when the account | ||
| // plane is unreachable, so availability is an invariant rather than | ||
| // something callers must wait for Validate to establish. | ||
| isAccAPIAvailable: true, |
There was a problem hiding this comment.
🟡 Suggestion: With UpdateAvailability removed, isAccAPIAvailable is now set to true at construction and never mutated, so IsAccountAPIAvailable() is a constant. Every remaining guard on it — account.go:51, account.go:93, account.go:110, workspaces.go:87, workspaces.go:109 — is dead code whose false branch can no longer be reached. Consider dropping the field, the accessor, and those branches so the OAuth-only invariant is expressed in the code rather than in a comment.
| // makes the account-plane check (host "accounts.*") fail so isAccAPIAvailable stays | ||
| // false while the workspace check still succeeds. | ||
| // makes the account-plane check (host "accounts.*") fail, which Validate treats as | ||
| // fatal. |
There was a problem hiding this comment.
🟡 Suggestion: Validate gained a second hard-failing account-plane call (ListWorkspaces), but no test covers it — TestValidateOAuthAccountCheckFailureReturnsError returns before reaching it, and the success-path test was deleted with the token-auth tests. Note that rolesTransport answers every request with {"roles":[]}, which will not unmarshal into ListWorkspaces' []Workspace, so adding coverage means teaching the transport to route on path, then asserting both the workspace-list failure and the all-green success path.
Full revert of #54, which implemented PAT (workspace-token) authentication and workspace-allowlist filtering.
Why
CXH-2109 records both of these as the connector's actual and expected behaviour:
For both, the correct resolution is to correct the documentation, not to build the feature. #54 built them instead. This reverts that and updates the docs to describe what the connector actually does.
PAT auth was already unreachable: #63 commented out the
workspace-tokenfield group and rejected the auth method inValidateConfig, but left the implementation in the tree behind docs describing it as temporarily unavailable and expected to return. OAuth2 is now the only authentication method, and the docs say so without qualification.Scope: verified byte-for-byte against pre-#54
These files are now identical to
b38a0beb^1, the commit before #54 merged:pkg/config/config.goworkspaces+workspace-tokensfields, field groups, constraints,ValidateConfigpkg/databricks/auth.goTokenAuth/NewTokenAuthpkg/connector/workspaces.gopkg/connector/helpers.gogroupGrantParent,isGroupNotFoundErrorpkg/connector/groups.gopkg/connector/roles.gopkg/connector/account.gopkg/connector/service-principals.gopkg/databricks/client.godropsIsTokenAuthandIsWorkspaceNameExcluded(both from #54) and keeps everything added after it.The only non-vendor files that still differ from pre-#54 are these six, and every one is post-#54 work belonging to another ticket, deliberately preserved:
pkg/connector/connector.go— CXH-2350's fatal account-API probe (from CXH-2350: surface workspace-token sync limits and fix PAT setup docs #57, not CXH-2166: implement PAT (workspace token) authentication #54)pkg/databricks/request.go— CXH-2350'swrapTransportAuthErrorpkg/databricks/client.go— Azure/GCP account-host fix, and CXH-2109: graceful 403 handling, workspace exclude flag, doc cleanup #55's 403 remedyvalidate_test.go,client_test.go,request_test.go— their tests--databricks-exclude-workspacespredates #54 and is untouched..github/workflows/ci.yamlis untouched.Docs
README.md,docs/connector.mdxanddocs/docs-info.mddrop both the PAT method and the workspace allowlist, along with the "temporarily unavailable / expected to return" framing. Scope narrowing is documented via--databricks-exclude-workspacesonly. The README CLI dump,conf.gen.goandconfig_schema.jsonare regenerated from the built binary.Self-hosted deployments using workspace tokens must move to OAuth2 (#63 already broke these at runtime). Deployments using
--workspacesmust switch to--databricks-exclude-workspaces.Two hunks of #54 deliberately kept: the group grant-parent fixes
The revert is complete except for two grant-parenting hunks, which are kept. Each is in its own commit so it can be reviewed, or dropped, independently of the revert.
pkg/connector/account.go— fixes a live bug.accountBuilder.Grantspassedresource.ParentResourceIdintogroupGrantExpansion. Thereresourceis the account itself, whoseParentResourceIdis nil, sogroupResourceIdreturnedgroup/<id>— while groups actually sync asaccount/<accountId>/group/<id>. The expansion named a resource no sync emits. #54 had incidentally fixed this; reverting it would regress againstmain.pkg/connector/service-principals.go— consistency.servicePrincipalBuilder.Grantspassedresource.ParentResourceIdintogroupResourceId. Unlike the account case this is non-nil, so it is not broken under OAuth today, but it derives the parent from a different source thangroupBuilderuses when syncing the group. Naming the parent from the service principal's own profile — as the rest of that method already does forworkspaceId— keeps the expansion and the synced group derived from one source.Both are kept because, unlike everything else in #54, neither is PAT or workspace-filtering work: they concern grant parenting under OAuth, which is the only auth method left.
Verification
go build ./...,go vet ./...,go test ./pkg/...all pass--workspacesand--workspace-tokensare gone from--help;--databricks-exclude-workspacesremainsrequired flag(s) "account-id", "databricks-client-id", "databricks-client-secret" not set(exit 2)workspace-token/WORKSPACE_TOKENS/TokenAuth/BATON_WORKSPACESis clean apart from the README line stating PAT is not supportedgolangci-lintnot run locally — the installed version rejects this repo's--out-formatflag, so CI is the first lint signal🤖 Generated with Claude Code
Review round: dead availability machinery removed
Two further commits, in response to review. Neither is part of the revert proper; both clean up state the revert left stranded.
db1a1e3a— drop the two-plane availability machinery. With OAuth the only auth method, the account plane is either reachable orValidatehard-fails, so the account/workspace availability split no longer carries information:Validate's per-workspaceListRolesloop setisWSAPIAvailableunconditionally inside its own body, so it reduced tolen(workspaces) > 0, and onlyDebug-logged failures — one sequential API call per workspace on every sync start, for no signal. Removed.ListWorkspacesstays: every sync depends on it.IsWorkspaceAPIAvailable()had no callers anywhere in the repo. It,isWSAPIAvailableandUpdateAvailabilityare gone.isAccAPIAvailableis now set inNewClientrather than only flipping whenValidateruns, so the five branches still gating sync output on it no longer depend on call ordering. The branches themselves are left in place — deleting them touches sync-output paths, which is beyond this PR.Same commit also drops
prepareClientAuth's two dead parameters, restores the blank identifier forNewConnector's unused*cli.ConnectorOpts, and removes the redundantWarnbefore returning an already-wrapped error fromNewConnector— the last of these was #54'sc29d543ecleanup that the revert had pulled back in by mistake.cf998aec—rolesTransport's doc comment no longer referenced reachable behaviour.One review request declined, with evidence
Restoring #54's
isGroupNotFoundErrorskip guard ingroups.gowas requested on the grounds that it is a general improvement like the two kept grant-parent hunks. It isn't reachable after this revert: the only builder that ever parented groups under a workspace isminimalWorkspaceResource, which exists for token auth by its own doc comment and is reverted here. The survivingworkspaceResourceannotates onlyroleas a child (as it did atb38a0beb^1), sogroupBuilder.Listis only ever called with the account as parent andworkspaceIdis always empty. Full reasoning is on the thread.