package pluginutils import ( "fmt" "slices" "strings" "github.com/grafana/grafana/pkg/plugins" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" ) var ( allowedCoreActions = map[string]string{ "plugins:write": "plugins:id:", "plugins.app:access": "plugins:id:", "folders:create": "folders:uid:", "folders:read": "folders:uid:", "folders:write": "folders:uid:", "folders:delete": "folders:uid:", "folders.permissions:read": "folders:uid:", "folders.permissions:write": "folders:uid:", } allowedActionSets = []string{"folders:view", "folders:edit", "folders:admin"} ) // ValidatePluginPermissions errors when a permission does not match expected pattern for plugins func ValidatePluginPermissions(pluginID string, permissions []ac.Permission) error { for i := range permissions { scopePrefix, isCore := allowedCoreActions[permissions[i].Action] if isCore { if permissions[i].Scope != scopePrefix+pluginID { return &ac.ErrorScopeTarget{Action: permissions[i].Action, Scope: permissions[i].Scope, ExpectedScope: scopePrefix + pluginID} } // Prevent any unlikely injection permissions[i].Scope = scopePrefix + pluginID continue } if err := ValidatePluginAction(pluginID, permissions[i].Action); err != nil { return err } } return nil } func ValidatePluginAction(pluginID, action string) error { if !strings.HasPrefix(action, pluginID+":") && !strings.HasPrefix(action, pluginID+".") { return &ac.ErrorActionPrefixMissing{Action: action, Prefixes: []string{pluginaccesscontrol.ActionAppAccess, pluginID + ":", pluginID + "."}} } return nil } // ValidatePluginActionSet errors when a actionset does not match expected pattern for plugins // - action set should be one of the allow-listed action sets (currently only folder action sets are supported for plugins) // - actions should have the pluginID prefix func ValidatePluginActionSet(pluginID string, actionSet plugins.ActionSet) error { if !slices.Contains(allowedActionSets, actionSet.Action) { return ac.ErrActionSetValidationFailed.Errorf("currently only folder and dashboard action sets are supported, provided action set %s is not a folder or dashboard action set", actionSet.Action) } // verify that actions have the pluginID prefix, plugins are only allowed to register actions for the plugin for _, action := range actionSet.Actions { if err := ValidatePluginAction(pluginID, action); err != nil { return err } } return nil } // ValidatePluginRole errors when a plugin role does not match expected pattern // or doesn't have permissions matching the expected pattern. func ValidatePluginRole(pluginID string, role ac.RoleDTO) error { if pluginID == "" { return ac.ErrPluginIDRequired } if role.DisplayName == "" { return &ac.ErrorRoleNameMissing{} } if !strings.HasPrefix(role.Name, ac.PluginRolePrefix+pluginID+":") { return &ac.ErrorRolePrefixMissing{Role: role.Name, Prefixes: []string{ac.PluginRolePrefix + pluginID + ":"}} } return ValidatePluginPermissions(pluginID, role.Permissions) } func ToRegistrations(pluginID, pluginName string, regs []plugins.RoleRegistration) []ac.RoleRegistration { res := make([]ac.RoleRegistration, 0, len(regs)) for i := range regs { res = append(res, ac.RoleRegistration{ Role: ac.RoleDTO{ Version: 1, Name: roleName(pluginID, regs[i].Role.Name), DisplayName: regs[i].Role.Name, Description: regs[i].Role.Description, Group: pluginName, Permissions: toPermissions(regs[i].Role.Permissions), OrgID: ac.GlobalOrgID, }, Grants: regs[i].Grants, }) } return res } // PluginIDFromName extracts the plugin ID from the role name func PluginIDFromName(roleName string) string { if !strings.HasPrefix(roleName, ac.PluginRolePrefix) { return "" } pluginID := strings.Builder{} for _, c := range roleName[len(ac.PluginRolePrefix):] { if c == ':' { break } pluginID.WriteRune(c) } return pluginID.String() } func roleName(pluginID, roleName string) string { return fmt.Sprintf("%v%v:%v", ac.PluginRolePrefix, pluginID, strings.ReplaceAll(strings.ToLower(roleName), " ", "-")) } func toPermissions(perms []plugins.Permission) []ac.Permission { res := make([]ac.Permission, 0, len(perms)) for i := range perms { res = append(res, ac.Permission{Action: perms[i].Action, Scope: perms[i].Scope}) } return res }