| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- 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 argumentIndex := range action.Arguments {
- arg := &action.Arguments[argumentIndex]
- 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(arg, value, allowed)
- }
- normalized := normalizeEntityArgumentValue(arg, value)
- _, ok := allowed[normalized]
- 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 normalizeEntityArgumentValue(arg *config.ActionArgument, value string) string {
- if arg == nil || arg.Entity == "" || len(arg.Choices) != 1 {
- return value
- }
- if resolved, ok := entityChoiceValueForTitle(arg, value); ok {
- return resolved
- }
- return value
- }
- func entityChoiceValueForTitle(arg *config.ActionArgument, title string) (string, bool) {
- for _, ent := range entities.GetEntityInstancesOrdered(arg.Entity) {
- expandedTitle := tpl.ParseTemplateOfActionBeforeExec(arg.Choices[0].Title, ent)
- if title != expandedTitle {
- continue
- }
- return tpl.ParseTemplateOfActionBeforeExec(arg.Choices[0].Value, ent), true
- }
- return "", false
- }
- func checklistEntityValuesAllowed(arg *config.ActionArgument, value string, allowed map[string]struct{}) bool {
- segments, err := config.ParseChecklistValue(value)
- if err != nil || len(segments) == 0 {
- return false
- }
- for _, segment := range segments {
- normalized := normalizeEntityArgumentValue(arg, strings.TrimSpace(segment))
- if _, ok := allowed[normalized]; !ok {
- return false
- }
- }
- return true
- }
|