grafana/pkg/services/searchV2/allowed_actions.go

256 lines
6.2 KiB
Go
Raw Normal View History

2022-07-22 19:42:43 -05:00
package searchV2
import (
"context"
"encoding/json"
"errors"
"fmt"
2022-07-22 19:42:43 -05:00
"sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
2022-07-22 19:42:43 -05:00
"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"
2022-07-22 19:42:43 -05:00
)
func (s *StandardSearchService) addAllowedActionsField(ctx context.Context, orgId int64, user *user.SignedInUser, response *backend.DataResponse) error {
2022-07-22 19:42:43 -05:00
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) {
2022-07-22 19:42:43 -05:00
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))
2022-07-22 19:42:43 -05:00
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,
2022-07-22 19:42:43 -05:00
orgID int64, prefix string, resourceIDs []string) 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)
2022-07-22 19:42:43 -05:00
if !ok {
return nil, fmt.Errorf("invalid value for uid %s in ds_uid field: %s", uidField, dsUidField.At(i))
2022-07-22 19:42:43 -05:00
}
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
}