Skip to content

Commit b073505

Browse files
auth profiles: always validate SPOG profiles as account
ResolveConfigType used to route SPOG profiles with a real workspace_id to WorkspaceConfig, so auth profiles validated them with CurrentUser.Me. SPOG OAuth is account-scoped, so every token's audience is the account, and the workspace API rejects those tokens with `400 "Unable to load OAuth Config"` — flagging otherwise-functional profiles as invalid. Always classify SPOG as AccountConfig so validation goes through Workspaces.List, which the account-audience token can actually authenticate. The SPOG mock in newSPOGServer now returns 500 on /scim/v2/Me so any future regression that reintroduces the workspace branch fails the test. Co-authored-by: Isaac
1 parent 193be7f commit b073505

3 files changed

Lines changed: 23 additions & 21 deletions

File tree

cmd/auth/profiles_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ func TestProfilesDefaultMarker(t *testing.T) {
8080
}
8181

8282
// newSPOGServer creates a mock SPOG server that returns account-scoped OIDC.
83-
// It serves both validation endpoints since SPOG workspace profiles (with a
84-
// real workspace_id) need CurrentUser.Me, while account profiles need
85-
// Workspaces.List. The workspace-only newWorkspaceServer omits the account
86-
// endpoint to prove routing correctness for non-SPOG hosts.
83+
// It serves only the account validation endpoint: every SPOG profile must
84+
// route through Workspaces.List, regardless of workspace_id (see
85+
// ResolveConfigType). The workspace endpoint deliberately returns 500 so any
86+
// regression that routes a SPOG profile to CurrentUser.Me fails the test.
8787
func newSPOGServer(t *testing.T, accountID string) *httptest.Server {
8888
t.Helper()
8989
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -97,8 +97,7 @@ func newSPOGServer(t *testing.T, accountID string) *httptest.Server {
9797
case "/api/2.0/accounts/" + accountID + "/workspaces":
9898
_ = json.NewEncoder(w).Encode([]map[string]any{})
9999
case "/api/2.0/preview/scim/v2/Me":
100-
// SPOG workspace profiles also need CurrentUser.Me to succeed.
101-
_ = json.NewEncoder(w).Encode(map[string]any{"userName": "test-user"})
100+
http.Error(w, "SPOG profiles must validate via Workspaces.List, not CurrentUser.Me", http.StatusInternalServerError)
102101
default:
103102
w.WriteHeader(http.StatusNotFound)
104103
}
@@ -148,7 +147,10 @@ func TestProfileLoadSPOGConfigType(t *testing.T) {
148147
wantValid: true,
149148
},
150149
{
151-
name: "SPOG workspace profile validated as workspace",
150+
// Regression: this case used to route to CurrentUser.Me. The
151+
// SPOG mock now returns 500 on that endpoint, so anything other
152+
// than the account path produces wantValid=false.
153+
name: "SPOG workspace profile validated as account",
152154
host: spogServer.URL,
153155
accountID: "spog-acct",
154156
workspaceID: "ws-123",

libs/auth/config_type.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,21 @@ func IsSPOG(cfg *config.Config, accountID string) bool {
3838
return HasUnifiedHostSignal(cfg.DiscoveryURL)
3939
}
4040

41-
// ResolveConfigType determines the effective ConfigType for a resolved config.
42-
// The SDK's ConfigType() classifies based on the host URL prefix alone, which
43-
// misclassifies SPOG hosts (they don't match the accounts.* prefix). This
44-
// function additionally uses IsSPOG to detect SPOG hosts.
41+
// ResolveConfigType returns the effective ConfigType for a resolved config.
42+
// The SDK's ConfigType() never returns AccountConfig for SPOG hosts (they
43+
// don't match the accounts.* prefix and resolve to UnifiedHost). For SPOG
44+
// the OAuth issuer is account-scoped, so every token's audience is the
45+
// account and workspace-API validation is unreliable — return AccountConfig
46+
// regardless of workspace_id.
4547
//
4648
// The cfg must already be resolved (via EnsureResolved) before calling this.
4749
func ResolveConfigType(cfg *config.Config) config.ConfigType {
4850
configType := cfg.ConfigType()
4951
if configType == config.AccountConfig {
5052
return configType
5153
}
52-
53-
if !IsSPOG(cfg, cfg.AccountID) {
54-
return configType
55-
}
56-
57-
if cfg.WorkspaceID != "" && cfg.WorkspaceID != WorkspaceIDNone {
58-
return config.WorkspaceConfig
54+
if IsSPOG(cfg, cfg.AccountID) {
55+
return config.AccountConfig
5956
}
60-
return config.AccountConfig
57+
return configType
6158
}

libs/auth/config_type_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,17 @@ func TestResolveConfigType(t *testing.T) {
4848
want: config.AccountConfig,
4949
},
5050
{
51-
name: "SPOG account-scoped OIDC with workspace routes to WorkspaceConfig",
51+
// SPOG OAuth is account-scoped, so even profiles with a real
52+
// workspace_id route to AccountConfig — the token's audience is
53+
// the account, and workspace-API validation can't authenticate it.
54+
name: "SPOG with real workspace_id routes to AccountConfig",
5255
cfg: &config.Config{
5356
Host: "https://spog.databricks.com",
5457
AccountID: "acct-123",
5558
WorkspaceID: "ws-456",
5659
DiscoveryURL: "https://spog.databricks.com/oidc/accounts/acct-123/.well-known/oauth-authorization-server",
5760
},
58-
want: config.WorkspaceConfig,
61+
want: config.AccountConfig,
5962
},
6063
{
6164
name: "SPOG account-scoped OIDC with workspace_id=none routes to AccountConfig",

0 commit comments

Comments
 (0)