Files
grafana/pkg/services/sqlstore/migrations/ualert/permissions.go
T
Sofia Papagiannaki 5e534d212c Alerting: Fix alert permissions migration (#35869)
* Alerting: Fix alert permissions migration

for MySQL >= 5.7 and PostgreSQL

* Do not use import struct
2021-06-17 15:49:56 +03:00

193 lines
4.8 KiB
Go

package ualert
import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
)
type roleType string
const (
ROLE_VIEWER roleType = "Viewer"
ROLE_EDITOR roleType = "Editor"
ROLE_ADMIN roleType = "Admin"
)
func (r roleType) IsValid() bool {
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}
type permissionType int
type dashboardAcl struct {
// nolint:stylecheck
Id int64
OrgID int64 `xorm:"org_id"`
DashboardID int64 `xorm:"dashboard_id"`
UserID int64 `xorm:"user_id"`
TeamID int64 `xorm:"team_id"`
Role *roleType // pointer to be nullable
Permission permissionType
Created time.Time
Updated time.Time
}
// getOrCreateGeneralFolder returns the general folder under the specific organisation
// If the general folder does not exist it creates it.
func (m *migration) getOrCreateGeneralFolder(orgID int64) (*dashboard, error) {
// there is a unique constraint on org_id, folder_id, title
// there are no nested folders so the parent folder id is always 0
dashboard := dashboard{OrgId: orgID, FolderId: 0, Title: GENERAL_FOLDER}
has, err := m.sess.Get(&dashboard)
if err != nil {
return nil, err
} else if !has {
// create folder
result, err := m.createFolder(orgID, GENERAL_FOLDER)
if err != nil {
return nil, err
}
return result, nil
}
return &dashboard, nil
}
// based on sqlstore.saveDashboard()
// it should be called from inside a transaction
func (m *migration) createFolder(orgID int64, title string) (*dashboard, error) {
cmd := saveFolderCommand{
OrgId: orgID,
FolderId: 0,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": title,
}),
}
dash := cmd.getDashboardModel()
uid, err := m.generateNewDashboardUid(dash.OrgId)
if err != nil {
return nil, err
}
dash.setUid(uid)
parentVersion := dash.Version
dash.setVersion(1)
dash.Created = time.Now()
dash.CreatedBy = FOLDER_CREATED_BY
dash.Updated = time.Now()
dash.UpdatedBy = FOLDER_CREATED_BY
metrics.MApiDashboardInsert.Inc()
if _, err = m.sess.Insert(dash); err != nil {
return nil, err
}
dashVersion := &models.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
// insert version entry
if _, err := m.sess.Insert(dashVersion); err != nil {
return nil, err
}
return dash, nil
}
func (m *migration) generateNewDashboardUid(orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := util.GenerateShortUID()
exists, err := m.sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.Dashboard{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", models.ErrDashboardFailedGenerateUniqueUid
}
// based on SQLStore.UpdateDashboardACL()
// it should be called from inside a transaction
func (m *migration) setACL(orgID int64, dashboardID int64, items []*dashboardAcl) error {
if dashboardID <= 0 {
return fmt.Errorf("folder id must be greater than zero for a folder permission")
}
for _, item := range items {
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing
}
// unset Id so that the new record will get a different one
item.Id = 0
item.OrgID = orgID
item.DashboardID = dashboardID
item.Created = time.Now()
item.Updated = time.Now()
m.sess.Nullable("user_id", "team_id")
if _, err := m.sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true}
_, err := m.sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
}
// based on SQLStore.GetDashboardAclInfoList()
func (m *migration) getACL(orgID, dashboardID int64) ([]*dashboardAcl, error) {
var err error
falseStr := m.mg.Dialect.BooleanStr(false)
result := make([]*dashboardAcl, 0)
rawSQL := `
-- get distinct permissions for the dashboard and its parent folder
SELECT DISTINCT
da.id,
da.user_id,
da.team_id,
da.permission,
da.role
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions --
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
err = m.sess.SQL(rawSQL, orgID, dashboardID).Find(&result)
return result, err
}