api_entity_argument_acl.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package api
  2. import (
  3. "fmt"
  4. "strings"
  5. "connectrpc.com/connect"
  6. authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
  7. config "github.com/OliveTin/OliveTin/internal/config"
  8. "github.com/OliveTin/OliveTin/internal/entities"
  9. "github.com/OliveTin/OliveTin/internal/executor"
  10. "github.com/OliveTin/OliveTin/internal/tpl"
  11. )
  12. // errUnlessStartEntityAccessAllowed enforces entity-type view ACL on the binding
  13. // and rejects entity-backed argument values the user may not use.
  14. func (api *oliveTinAPI) errUnlessStartEntityAccessAllowed(user *authpublic.AuthenticatedUser, binding *executor.ActionBinding, args map[string]string) error {
  15. if err := api.errUnlessBindingEntityTypeAllowed(user, binding); err != nil {
  16. return err
  17. }
  18. if binding == nil {
  19. return nil
  20. }
  21. return api.errUnlessEntityArgumentsAllowed(user, binding.Action, args)
  22. }
  23. // errUnlessEntityArgumentsAllowed rejects starts that use entity-backed arguments
  24. // the user may not view, or guessed values that are not in the allowed choice set.
  25. func (api *oliveTinAPI) errUnlessEntityArgumentsAllowed(user *authpublic.AuthenticatedUser, action *config.Action, args map[string]string) error {
  26. if action == nil {
  27. return nil
  28. }
  29. for i := range action.Arguments {
  30. arg := &action.Arguments[i]
  31. if arg.Entity == "" {
  32. continue
  33. }
  34. if err := api.errUnlessEntityArgumentAllowed(user, arg, args[arg.Name]); err != nil {
  35. return err
  36. }
  37. }
  38. return nil
  39. }
  40. func isEntityBackedArgument(arg *config.ActionArgument) bool {
  41. return arg != nil && arg.Entity != "" && len(arg.Choices) == 1
  42. }
  43. func isMalformedEntityArgument(arg *config.ActionArgument) bool {
  44. return arg != nil && arg.Entity != "" && len(arg.Choices) != 1
  45. }
  46. func (api *oliveTinAPI) errUnlessEntityArgumentAllowed(user *authpublic.AuthenticatedUser, arg *config.ActionArgument, value string) error {
  47. if err := errUnlessEntityArgumentShapeAllowed(arg); err != nil {
  48. return err
  49. }
  50. if !isEntityBackedArgument(arg) {
  51. return nil
  52. }
  53. if !api.userCanViewEntityType(user, arg.Entity) {
  54. return connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
  55. }
  56. if err := errUnlessEntityArgumentValueAllowed(arg, value); err != nil {
  57. return connect.NewError(connect.CodeInvalidArgument, err)
  58. }
  59. return nil
  60. }
  61. func errUnlessEntityArgumentShapeAllowed(arg *config.ActionArgument) error {
  62. if !isMalformedEntityArgument(arg) {
  63. return nil
  64. }
  65. return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("argument %q with entity must define exactly one choice template", arg.Name))
  66. }
  67. func errUnlessEntityArgumentValueAllowed(arg *config.ActionArgument, value string) error {
  68. if isMalformedEntityArgument(arg) {
  69. return fmt.Errorf("argument %q with entity must define exactly one choice template", arg.Name)
  70. }
  71. if !isEntityBackedArgument(arg) {
  72. return nil
  73. }
  74. value = strings.TrimSpace(value)
  75. if value == "" {
  76. return nil
  77. }
  78. if !entityArgumentValueAllowed(arg, value) {
  79. return fmt.Errorf("argument %q is not a permitted entity value", arg.Name)
  80. }
  81. return nil
  82. }
  83. func entityArgumentValueAllowed(arg *config.ActionArgument, value string) bool {
  84. allowed := entityArgumentAllowedValues(arg)
  85. if strings.EqualFold(arg.Type, "checklist") {
  86. return checklistEntityValuesAllowed(value, allowed)
  87. }
  88. _, ok := allowed[value]
  89. return ok
  90. }
  91. func entityArgumentAllowedValues(arg *config.ActionArgument) map[string]struct{} {
  92. allowed := make(map[string]struct{})
  93. if arg == nil || len(arg.Choices) != 1 {
  94. return allowed
  95. }
  96. for _, ent := range entities.GetEntityInstancesOrdered(arg.Entity) {
  97. resolved := tpl.ParseTemplateOfActionBeforeExec(arg.Choices[0].Value, ent)
  98. if resolved == "" {
  99. continue
  100. }
  101. allowed[resolved] = struct{}{}
  102. }
  103. return allowed
  104. }
  105. func checklistEntityValuesAllowed(value string, allowed map[string]struct{}) bool {
  106. parts := strings.Split(value, ",")
  107. sawItem := false
  108. for _, part := range parts {
  109. part = strings.TrimSpace(part)
  110. if part == "" {
  111. continue
  112. }
  113. sawItem = true
  114. if _, ok := allowed[part]; !ok {
  115. return false
  116. }
  117. }
  118. return sawItem
  119. }