The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/sqlstore/migrations/ualert/permissions.go

192 lines
4.8 KiB

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
}