| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package api
- import (
- "fmt"
- "strings"
- "connectrpc.com/connect"
- authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
- config "github.com/OliveTin/OliveTin/internal/config"
- "github.com/OliveTin/OliveTin/internal/entities"
- "github.com/OliveTin/OliveTin/internal/executor"
- "github.com/OliveTin/OliveTin/internal/tpl"
- )
- // errUnlessStartEntityAccessAllowed enforces entity-type view ACL on the binding
- // and rejects entity-backed argument values the user may not use.
- func (api *oliveTinAPI) errUnlessStartEntityAccessAllowed(user *authpublic.AuthenticatedUser, binding *executor.ActionBinding, args map[string]string) error {
- if err := api.errUnlessBindingEntityTypeAllowed(user, binding); err != nil {
- return err
- }
- if binding == nil {
- return nil
- }
- return api.errUnlessEntityArgumentsAllowed(user, binding.Action, args)
- }
- // errUnlessEntityArgumentsAllowed rejects starts that use entity-backed arguments
- // the user may not view, or guessed values that are not in the allowed choice set.
- func (api *oliveTinAPI) errUnlessEntityArgumentsAllowed(user *authpublic.AuthenticatedUser, action *config.Action, args map[string]string) error {
- if action == nil {
- return nil
- }
- for i := range action.Arguments {
- arg := &action.Arguments[i]
- if arg.Entity == "" {
- continue
- }
- if err := api.errUnlessEntityArgumentAllowed(user, arg, args[arg.Name]); err != nil {
- return err
- }
- }
- return nil
- }
- func isEntityBackedArgument(arg *config.ActionArgument) bool {
- return arg != nil && arg.Entity != "" && len(arg.Choices) == 1
- }
- func isMalformedEntityArgument(arg *config.ActionArgument) bool {
- return arg != nil && arg.Entity != "" && len(arg.Choices) != 1
- }
- func (api *oliveTinAPI) errUnlessEntityArgumentAllowed(user *authpublic.AuthenticatedUser, arg *config.ActionArgument, value string) error {
- if err := errUnlessEntityArgumentShapeAllowed(arg); err != nil {
- return err
- }
- if !isEntityBackedArgument(arg) {
- return nil
- }
- if !api.userCanViewEntityType(user, arg.Entity) {
- return connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
- }
- if err := errUnlessEntityArgumentValueAllowed(arg, value); err != nil {
- return connect.NewError(connect.CodeInvalidArgument, err)
- }
- return nil
- }
- func errUnlessEntityArgumentShapeAllowed(arg *config.ActionArgument) error {
- if !isMalformedEntityArgument(arg) {
- return nil
- }
- return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("argument %q with entity must define exactly one choice template", arg.Name))
- }
- func errUnlessEntityArgumentValueAllowed(arg *config.ActionArgument, value string) error {
- if isMalformedEntityArgument(arg) {
- return fmt.Errorf("argument %q with entity must define exactly one choice template", arg.Name)
- }
- if !isEntityBackedArgument(arg) {
- return nil
- }
- value = strings.TrimSpace(value)
- if value == "" {
- return nil
- }
- if !entityArgumentValueAllowed(arg, value) {
- return fmt.Errorf("argument %q is not a permitted entity value", arg.Name)
- }
- return nil
- }
- func entityArgumentValueAllowed(arg *config.ActionArgument, value string) bool {
- allowed := entityArgumentAllowedValues(arg)
- if strings.EqualFold(arg.Type, "checklist") {
- return checklistEntityValuesAllowed(value, allowed)
- }
- _, ok := allowed[value]
- return ok
- }
- func entityArgumentAllowedValues(arg *config.ActionArgument) map[string]struct{} {
- allowed := make(map[string]struct{})
- if arg == nil || len(arg.Choices) != 1 {
- return allowed
- }
- for _, ent := range entities.GetEntityInstancesOrdered(arg.Entity) {
- resolved := tpl.ParseTemplateOfActionBeforeExec(arg.Choices[0].Value, ent)
- if resolved == "" {
- continue
- }
- allowed[resolved] = struct{}{}
- }
- return allowed
- }
- func checklistEntityValuesAllowed(value string, allowed map[string]struct{}) bool {
- parts := strings.Split(value, ",")
- sawItem := false
- for _, part := range parts {
- part = strings.TrimSpace(part)
- if part == "" {
- continue
- }
- sawItem = true
- if _, ok := allowed[part]; !ok {
- return false
- }
- }
- return sawItem
- }
|