Dashboard: Fix editor specific permissions in /api (#113292)

This commit is contained in:
Stephanie Hingtgen
2025-10-31 09:03:35 -05:00
committed by GitHub
parent f6e4dd9b0c
commit ea90bdff9c
2 changed files with 137 additions and 2 deletions
@@ -676,11 +676,14 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
// Validate folder
if dash.FolderID != 0 || dash.FolderUID != "" { // nolint:staticcheck
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
// use a background requester, because the user may have been granted edit access to that specific dashboard, but
// not have access to the parent folder. we should still allow editing in that case.
ctxIdentity, backgroundRequester := identity.WithServiceIdentity(ctx, dash.OrgID)
folder, err := dr.folderService.Get(ctxIdentity, &folder.GetFolderQuery{
OrgID: dash.OrgID,
UID: &dash.FolderUID,
ID: &dash.FolderID, // nolint:staticcheck
SignedInUser: dto.User,
SignedInUser: backgroundRequester,
})
if err != nil {
return nil, err
@@ -1211,5 +1211,137 @@ func TestIntegrationDashboardServicePermissions(t *testing.T) {
assert.Contains(t, foundTitles, "dashboard in parent", "Should return dashboard in parent folder")
assert.NotContains(t, foundTitles, "dashboard in child", "Should not return dashboard in child folder")
})
t.Run("user with edit permissions on dashboard but not on parent folder should be able to edit the dashboard", func(t *testing.T) {
testFolder := createFolder(t, grafanaListedAddr, "restricted folder")
testDash := createDashboard(t, grafanaListedAddr, "dashboard with specific permissions", testFolder.ID, testFolder.UID) // nolint:staticcheck
restrictedUserID := tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleNone),
Login: "restricteduser",
Password: "restricteduser",
IsAdmin: false,
})
setDashboardPermissions := func(t *testing.T, grafanaListedAddr string, dashboardUID string, permissions []map[string]interface{}) {
t.Helper()
permissionPayload := map[string]interface{}{
"items": permissions,
}
payloadBytes, err := json.Marshal(permissionPayload)
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/%s/permissions", grafanaListedAddr, dashboardUID)
req, err := http.NewRequest(http.MethodPost, u, bytes.NewBuffer(payloadBytes))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
err = resp.Body.Close()
require.NoError(t, err)
}
editPermissions := []map[string]interface{}{
{
"permission": 2,
"userId": restrictedUserID,
},
}
setDashboardPermissions(t, grafanaListedAddr, testDash.UID, editPermissions)
// user cannot access the folder
u := fmt.Sprintf("http://restricteduser:restricteduser@%s/api/folders/%s", grafanaListedAddr, testFolder.UID)
resp, err := http.Get(u) // nolint:gosec
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode, "User should not have access to the folder")
err = resp.Body.Close()
require.NoError(t, err)
// but can edit the dashboard
dashboardPayload := map[string]interface{}{
"dashboard": map[string]interface{}{
"uid": testDash.UID,
"title": "Updated title by restricted user",
},
"folderUid": testFolder.UID,
"overwrite": true,
}
resp, err = postDashboard(t, grafanaListedAddr, "restricteduser", "restricteduser", dashboardPayload)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "User should be able to edit dashboard even without folder access")
err = resp.Body.Close()
require.NoError(t, err)
})
t.Run("user with no permissions on dashboard or parent folder should not be able to edit the dashboard", func(t *testing.T) {
testFolder := createFolder(t, grafanaListedAddr, "restricted folder")
testDash := createDashboard(t, grafanaListedAddr, "dashboard with specific permissions", testFolder.ID, testFolder.UID) // nolint:staticcheck
// user cannot access the folder
u := fmt.Sprintf("http://restricteduser:restricteduser@%s/api/folders/%s", grafanaListedAddr, testFolder.UID)
resp, err := http.Get(u) // nolint:gosec
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode, "User should not have access to the folder")
err = resp.Body.Close()
require.NoError(t, err)
// and cannot edit the dashboard
dashboardPayload := map[string]interface{}{
"dashboard": map[string]interface{}{
"uid": testDash.UID,
"title": "Updated title by restricted user",
},
"folderUid": testFolder.UID,
"overwrite": true,
}
resp, err = postDashboard(t, grafanaListedAddr, "restricteduser", "restricteduser", dashboardPayload)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode, "User should not be able to edit dashboard without any permissions")
err = resp.Body.Close()
require.NoError(t, err)
})
t.Run("user with edit permissions on parent folder should be able to edit dashboard through permission inheritance", func(t *testing.T) {
inheritedPermissionsUserID := tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleNone),
Login: "inheriteduser",
Password: "inheriteduser",
IsAdmin: false,
})
testFolder := createFolder(t, grafanaListedAddr, "folder with inherited permissions")
editFolderPermissions := []map[string]interface{}{
{
"permission": 2,
"userId": inheritedPermissionsUserID,
},
}
setFolderPermissions(t, grafanaListedAddr, testFolder.UID, editFolderPermissions)
// create dashboard in the folder without specific dashboard permissions
testDash := createDashboard(t, grafanaListedAddr, "dashboard inheriting permissions", testFolder.ID, testFolder.UID) // nolint:staticcheck
u := fmt.Sprintf("http://inheriteduser:inheriteduser@%s/api/folders/%s", grafanaListedAddr, testFolder.UID)
resp, err := http.Get(u) // nolint:gosec
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "User should have access to the folder")
err = resp.Body.Close()
require.NoError(t, err)
// and can edit the dashboard by inheriting permissions from the folder
dashboardPayload := map[string]interface{}{
"dashboard": map[string]interface{}{
"uid": testDash.UID,
"title": "Updated title via inherited permissions",
},
"folderUid": testFolder.UID,
"overwrite": true,
}
resp, err = postDashboard(t, grafanaListedAddr, "inheriteduser", "inheriteduser", dashboardPayload)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "User should be able to edit dashboard through inherited folder permissions")
err = resp.Body.Close()
require.NoError(t, err)
})
})
}