Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions libs/testserver/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@
"github.com/databricks/databricks-sdk-go/service/workspace"
)

// workspaceRoots are the top-level directories the real workspace exposes.
var workspaceRoots = []string{"/Users/", "/Repos/", "/Shared/"}

// isValidWorkspaceParentPath reports whether p is a parent_path cloud accepts:
// an absolute, canonical path (no traversal or control chars) under a workspace
// root. The optional "/Workspace" mount prefix is ignored for the root check.
func isValidWorkspaceParentPath(p string) bool {
if !strings.HasPrefix(p, "/") || p != path.Clean(p) {
return false
}
for _, r := range p {
if r < 0x20 || r == 0x7f {
return false
}
}
if p == "/Workspace" || strings.HasPrefix(p, "/Workspace/") {
p = strings.TrimPrefix(p, "/Workspace")
}
for _, root := range workspaceRoots {
if strings.HasPrefix(p, root) {
return true
}
}
return false
}

// Generate 32 character hex string for dashboard ID
func generateDashboardId() (string, error) {
randomBytes := make([]byte, 16)
Expand Down Expand Up @@ -109,6 +135,16 @@
dashboard.ParentPath = "/Users/" + s.CurrentUser().UserName
}

// Reject non-workspace paths like cloud does; storing them verbatim surfaces as drift.
if !isValidWorkspaceParentPath(dashboard.ParentPath) {
return Response{
StatusCode: 400,
Body: map[string]string{
"message": fmt.Sprintf("Invalid parent path: %s", dashboard.ParentPath),

Check failure on line 143 in libs/testserver/dashboards.go

View workflow job for this annotation

GitHub Actions / lint

string-format: fmt.Sprintf can be replaced with string concatenation (perfsprint)
},
}
}

if _, ok := s.directories[dashboard.ParentPath]; !ok {
return Response{
StatusCode: 404,
Expand Down
40 changes: 40 additions & 0 deletions libs/testserver/dashboards_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package testserver_test

import (
"net/http"
"strings"
"testing"

"github.com/databricks/cli/libs/testserver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func createDashboard(t *testing.T, baseURL, parentPath string) int {
t.Helper()
body := `{"display_name":"d","parent_path":"` + parentPath + `","warehouse_id":"w"}`
req, _ := http.NewRequest(http.MethodPost, baseURL+"/api/2.0/lakeview/dashboards", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer test-token")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
return resp.StatusCode
}

// A non-workspace parent_path is a 400, even when the directory exists: "/etc/passwd"
// isn't under a workspace root, so it's rejected rather than stored as drift.
func TestDashboardCreateRejectsInvalidParentPath(t *testing.T) {
server := testserver.New(t)
testserver.AddDefaultHandlers(server)

mkdirs(t, server.URL, "/Users/dev")
mkdirs(t, server.URL, "/Workspace/Users/dev")
assert.Equal(t, 200, createDashboard(t, server.URL, "/Users/dev"))
assert.Equal(t, 200, createDashboard(t, server.URL, "/Workspace/Users/dev"))

mkdirs(t, server.URL, "/etc/passwd")
assert.Equal(t, 400, createDashboard(t, server.URL, "/etc/passwd"))
assert.Equal(t, 400, createDashboard(t, server.URL, "/Users/../etc"))
assert.Equal(t, 400, createDashboard(t, server.URL, "relative/path"))
assert.Equal(t, 400, createDashboard(t, server.URL, "/Users/\x01dev"))
}
Loading