You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

93 lines
3.2 KiB

  1. #lang racket
  2. (provide (struct-out action)
  3. minimum-access-mask
  4. maximum-access-mask
  5. run-action
  6. mask-join
  7. apply-mask
  8. is-action-set?
  9. is-mask-for?)
  10. ;; The type for actions that can be run on a resource. Actions are
  11. ;; generally, but not always, unique to a single resource type. `fun`
  12. ;; is the actual function that is to be run, and must take two
  13. ;; parameters, one with the resource data, and a hash with any
  14. ;; additional required parameters, e.g. new data when editing
  15. ;; something.
  16. ;; See resource.rkt for examples
  17. (struct
  18. action
  19. (id
  20. fun
  21. req-params)
  22. #:transparent)
  23. ;; Run the given action on the provided data, with the given additional
  24. ;; parameters.
  25. (define (run-action action data params)
  26. ;; TODO add check that action matches resource type by comparing
  27. ;; keys in params to req-params field in action
  28. ((action-fun action) data params))
  29. ;; An action set is the hash-of-lists-of-functions that define the
  30. ;; actions available on a resource type. The hash level are the
  31. ;; different "branches" of actions, while each branch is a list
  32. ;; of actions of increasing required privilege.
  33. (define (is-action-set? actions)
  34. (and (hash? actions)
  35. (for/and ([(k v) (in-hash actions)])
  36. (and (dict? v) (list? v)))))
  37. ;; A mask is a map from action branches to action IDs. In a sense
  38. ;; it is a subset of the action set; though it doesn't actually
  39. ;; contain the actions, it does describe which actions can be used.
  40. ;; True if a given mask is a mask for the given action set; this is
  41. ;; the case if both have the same keys, and each value in the mask
  42. ;; exists in the list at the corresponding key in the action set hash.
  43. (define (is-mask-for? actions mask)
  44. (for/and ([(k v) (in-hash actions)])
  45. (not (false? (assoc (dict-ref mask k) v)))))
  46. ;; Return the mask for an action set that provides the least possible
  47. ;; level of access, i.e. only the first action in each branch.
  48. (define (minimum-access-mask actions)
  49. (if (hash? actions)
  50. (for/hash ([(k v) (in-hash actions)])
  51. (values k (caar v)))
  52. (error 'not-action-set)))
  53. ;; Return the mask for an action set that provides full access.
  54. (define (maximum-access-mask actions)
  55. (if (hash? actions)
  56. (for/hash ([(k v) (in-hash actions)])
  57. (values k (car (last v))))
  58. (error 'not-action-set)))
  59. ;; Indexing function used to compare the access level of two actions
  60. ;; in a branch.
  61. (define (mask-index branch)
  62. (lambda (access-level)
  63. (index-of branch access-level
  64. (lambda (x y) (string=? (car x) y)))))
  65. ;; Return the "total" access that any number of masks for a given
  66. ;; action set cover. The access level for each branch is set to
  67. ;; the highest among the masks.
  68. (define (mask-join actions . masks)
  69. (for/hasheq ([(k v) (in-hash actions)])
  70. (values k (argmax (mask-index v)
  71. (map (curryr dict-ref k) masks)))))
  72. ;; Return a subset for the given action set as delimited by the mask.
  73. ;; The result is the accessible action set.
  74. (define (apply-mask actions mask)
  75. (if (is-mask-for? actions mask)
  76. (for/hash ([(k v) (in-hash actions)])
  77. (let ((ix (+ 1 ((mask-index v) (dict-ref mask k)))))
  78. (values k (take v ix))))
  79. (error 'incompatible-action-mask)))