acl.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package acl
  2. import (
  3. authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
  4. config "github.com/OliveTin/OliveTin/internal/config"
  5. log "github.com/sirupsen/logrus"
  6. "golang.org/x/exp/slices"
  7. )
  8. type PermissionBits int
  9. const (
  10. View PermissionBits = 1 << iota
  11. Exec
  12. Logs
  13. Kill
  14. )
  15. func (p PermissionBits) Has(permission PermissionBits) bool {
  16. return p&permission != 0
  17. }
  18. func logAclNotMatched(cfg *config.Config, aclFunction string, user *authpublic.AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
  19. if cfg.LogDebugOptions.AclNotMatched {
  20. log.WithFields(log.Fields{
  21. "User": user.Username,
  22. "Action": action.Title,
  23. "ACL": acl.Name,
  24. }).Debugf("%v - ACL Not Matched", aclFunction)
  25. }
  26. }
  27. func logAclMatched(cfg *config.Config, aclFunction string, user *authpublic.AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
  28. actionTitle := "N/A"
  29. if action != nil {
  30. actionTitle = action.Title
  31. }
  32. if cfg.LogDebugOptions.AclMatched {
  33. log.WithFields(log.Fields{
  34. "User": user.Username,
  35. "Action": actionTitle,
  36. "ACL": acl.Name,
  37. }).Debugf("%v - Matched ACL", aclFunction)
  38. }
  39. }
  40. func logAclNoneMatched(cfg *config.Config, aclFunction string, user *authpublic.AuthenticatedUser, action *config.Action, defaultPermission bool) {
  41. if cfg.LogDebugOptions.AclNoneMatched {
  42. log.WithFields(log.Fields{
  43. "User": user.Username,
  44. "Action": action.Title,
  45. "Default": defaultPermission,
  46. }).Debugf("%v - No ACLs Matched, returning default permission", aclFunction)
  47. }
  48. }
  49. func permissionsConfigToBits(permissions config.PermissionsList) PermissionBits {
  50. type permPair struct {
  51. enabled bool
  52. bit PermissionBits
  53. }
  54. permMap := []permPair{
  55. {permissions.View, View},
  56. {permissions.Exec, Exec},
  57. {permissions.Logs, Logs},
  58. {permissions.Kill, Kill},
  59. }
  60. var ret PermissionBits
  61. for _, perm := range permMap {
  62. if perm.enabled {
  63. ret |= perm.bit
  64. }
  65. }
  66. return ret
  67. }
  68. func aclCheck(requiredPermission PermissionBits, defaultValue bool, cfg *config.Config, aclFunction string, user *authpublic.AuthenticatedUser, action *config.Action) bool {
  69. relevantAcls := getRelevantAcls(cfg, action.Acls, user)
  70. if cfg.LogDebugOptions.AclCheckStarted {
  71. log.WithFields(log.Fields{
  72. "actionTitle": action.Title,
  73. "username": user.Username,
  74. "usergroupLine": user.UsergroupLine,
  75. "relevantAcls": len(relevantAcls),
  76. "requiredPermission": requiredPermission,
  77. }).Debugf("ACL check - %v", aclFunction)
  78. }
  79. for _, acl := range relevantAcls {
  80. permissionBits := permissionsConfigToBits(acl.Permissions)
  81. if permissionBits.Has(requiredPermission) {
  82. logAclMatched(cfg, aclFunction, user, action, acl)
  83. return true
  84. } else {
  85. logAclNotMatched(cfg, aclFunction, user, action, acl)
  86. }
  87. }
  88. logAclNoneMatched(cfg, aclFunction, user, action, cfg.DefaultPermissions.Logs)
  89. return defaultValue
  90. }
  91. // IsAllowedLogs checks if a AuthenticatedUser is allowed to view an action's logs
  92. func IsAllowedLogs(cfg *config.Config, user *authpublic.AuthenticatedUser, action *config.Action) bool {
  93. return aclCheck(Logs, cfg.DefaultPermissions.Logs, cfg, "isAllowedLogs", user, action)
  94. }
  95. // IsAllowedExec checks if a AuthenticatedUser is allowed to execute an Action
  96. func IsAllowedExec(cfg *config.Config, user *authpublic.AuthenticatedUser, action *config.Action) bool {
  97. return aclCheck(Exec, cfg.DefaultPermissions.Exec, cfg, "isAllowedExec", user, action)
  98. }
  99. // IsAllowedView checks if a User is allowed to view an Action
  100. func IsAllowedView(cfg *config.Config, user *authpublic.AuthenticatedUser, action *config.Action) bool {
  101. if action.Hidden {
  102. return false
  103. }
  104. return aclCheck(View, cfg.DefaultPermissions.View, cfg, "isAllowedView", user, action)
  105. }
  106. func IsAllowedKill(cfg *config.Config, user *authpublic.AuthenticatedUser, action *config.Action) bool {
  107. return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action)
  108. }
  109. func isACLRelevantToAction(cfg *config.Config, actionAcls []string, acl *config.AccessControlList, user *authpublic.AuthenticatedUser) bool {
  110. if !slices.Contains(user.Acls, acl.Name) {
  111. // If the user does not have this ACL, then it is not relevant
  112. return false
  113. }
  114. if acl.AddToEveryAction {
  115. return true
  116. }
  117. if slices.Contains(actionAcls, acl.Name) {
  118. return true
  119. }
  120. return false
  121. }
  122. func getRelevantAcls(cfg *config.Config, actionAcls []string, user *authpublic.AuthenticatedUser) []*config.AccessControlList {
  123. var ret []*config.AccessControlList
  124. for _, acl := range cfg.AccessControlLists {
  125. if isACLRelevantToAction(cfg, actionAcls, acl, user) {
  126. ret = append(ret, acl)
  127. }
  128. }
  129. return ret
  130. }