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/searchV2/allowed_actions.go

259 lines
6.3 KiB

package searchV2
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/user"
)
func (s *StandardSearchService) addAllowedActionsField(ctx context.Context, orgId int64, user *user.SignedInUser, response *backend.DataResponse) error {
references, err := getEntityReferences(response)
if err != nil {
return err
}
allAllowedActions, err := s.createAllowedActions(ctx, orgId, user, references)
if err != nil {
return err
}
if len(response.Frames) == 0 {
return errors.New("empty response")
}
frame := response.Frames[0]
allowedActionsField := data.NewFieldFromFieldType(data.FieldTypeJSON, len(allAllowedActions))
allowedActionsField.Name = "allowed_actions"
frame.Fields = append(frame.Fields, allowedActionsField)
for i, actions := range allAllowedActions {
js, _ := json.Marshal(actions)
jsb := json.RawMessage(js)
allowedActionsField.Set(i, jsb)
}
return nil
}
type allowedActions struct {
EntityKind entityKind `json:"kind"`
UID string `json:"uid"`
Actions []string `json:"actions"`
}
func (s *StandardSearchService) createAllowedActions(ctx context.Context, orgId int64, user *user.SignedInUser, references []entityReferences) ([][]allowedActions, error) {
uidsPerKind := make(map[entityKind][]string)
for _, refs := range references {
if _, ok := uidsPerKind[refs.entityKind]; !ok {
uidsPerKind[refs.entityKind] = []string{}
}
uidsPerKind[refs.entityKind] = append(uidsPerKind[refs.entityKind], refs.uid)
if len(refs.dsUids) > 0 {
if _, ok := uidsPerKind[entityKindDatasource]; !ok {
uidsPerKind[entityKindDatasource] = []string{}
}
uidsPerKind[entityKindDatasource] = append(uidsPerKind[entityKindDatasource], refs.dsUids...)
}
}
allowedActionsByUid := make(map[entityKind]map[string][]string)
for entKind, uids := range uidsPerKind {
if entKind == entityKindPanel {
emptyAllowedActions := make(map[string][]string)
for _, uid := range uids {
emptyAllowedActions[uid] = []string{}
}
allowedActionsByUid[entityKindPanel] = emptyAllowedActions
}
var prefix string
switch entKind {
case entityKindFolder:
prefix = dashboards.ScopeFoldersPrefix
case entityKindDatasource:
prefix = datasources.ScopePrefix
case entityKindDashboard:
prefix = dashboards.ScopeDashboardsPrefix
default:
continue
}
allowedActionsByUid[entKind] = s.getAllowedActionsByUid(ctx, user, orgId, prefix, uids)
}
dsActionsByUid, ok := allowedActionsByUid[entityKindDatasource]
if !ok {
dsActionsByUid = make(map[string][]string)
}
out := make([][]allowedActions, 0, len(references))
for _, ref := range references {
var actions []allowedActions
selfActions := make([]string, 0)
if selfKindActions, ok := allowedActionsByUid[ref.entityKind]; ok {
if self, ok := selfKindActions[ref.uid]; ok && len(self) > 0 {
selfActions = self
}
}
actions = append(actions, allowedActions{
EntityKind: ref.entityKind,
UID: ref.uid,
Actions: selfActions,
})
for _, dsUid := range ref.dsUids {
dsActions := make([]string, 0)
if dsAct, ok := dsActionsByUid[dsUid]; ok {
dsActions = dsAct
}
actions = append(actions, allowedActions{
EntityKind: entityKindDatasource,
UID: dsUid,
Actions: dsActions,
})
}
out = append(out, actions)
}
return out, nil
}
func (s *StandardSearchService) getAllowedActionsByUid(ctx context.Context, user *user.SignedInUser,
orgID int64, prefix string, resourceIDs []string) map[string][]string {
if s.ac.IsDisabled() {
return map[string][]string{}
}
if user.Permissions == nil {
return map[string][]string{}
}
permissions, ok := user.Permissions[orgID]
if !ok {
return map[string][]string{}
}
uidsAsMap := make(map[string]bool)
for _, uid := range resourceIDs {
uidsAsMap[uid] = true
}
out := make(map[string][]string)
resp := accesscontrol.GetResourcesMetadata(ctx, permissions, prefix, uidsAsMap)
for uid, meta := range resp {
var actions []string
for action := range meta {
actions = append(actions, action)
}
sort.Strings(actions)
out[uid] = actions
}
return out
}
type entityReferences struct {
entityKind entityKind
uid string
dsUids []string
}
func getEntityReferences(resp *backend.DataResponse) ([]entityReferences, error) {
if resp == nil {
return nil, errors.New("nil response")
}
if resp.Error != nil {
return nil, resp.Error
}
if len(resp.Frames) == 0 {
return nil, errors.New("empty response")
}
frame := resp.Frames[0]
kindField, idx := frame.FieldByName("kind")
if idx == -1 {
return nil, errors.New("no kind field")
}
dsUidField, idx := frame.FieldByName("ds_uid")
if idx == -1 {
return nil, errors.New("no ds_uid field")
}
uidField, idx := frame.FieldByName("uid")
if idx == -1 {
return nil, errors.New("no dash_uid field")
}
if dsUidField.Len() != uidField.Len() {
return nil, errors.New("mismatched lengths")
}
var out []entityReferences
for i := 0; i < dsUidField.Len(); i++ {
kind, ok := kindField.At(i).(string)
if !ok || kind == "" {
return nil, errors.New("invalid value in kind field")
}
uid, ok := uidField.At(i).(string)
if !ok || uid == "" {
return nil, errors.New("invalid value in uid field")
}
if entityKind(kind) != entityKindDashboard {
out = append(out, entityReferences{
entityKind: entityKind(kind),
uid: uid,
})
continue
}
uidField, ok := uidField.At(i).(string)
if !ok || uidField == "" {
return nil, errors.New("invalid value in dash_uid field")
}
rawDsUids, ok := dsUidField.At(i).(json.RawMessage)
if !ok {
return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uidField, dsUidField.At(i))
}
var uids []string
if rawDsUids != nil {
jsonValue, err := rawDsUids.MarshalJSON()
if err != nil {
return nil, err
}
err = json.Unmarshal(jsonValue, &uids)
if err != nil {
return nil, err
}
}
out = append(out, entityReferences{entityKind: entityKindDashboard, uid: uid, dsUids: uids})
}
return out, nil
}