A cloud-native LDAP server backed by a Consul datastore
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.

142 lines
3.0 KiB

  1. package main
  2. import (
  3. "fmt"
  4. "path"
  5. "strings"
  6. )
  7. type Login struct {
  8. user string
  9. groups []string
  10. }
  11. type ACL []ACLEntry
  12. type ACLEntry struct {
  13. // The authenticated user (or ANONYMOUS if not authenticated) must match this string
  14. user string
  15. // For each of this groups, the authenticated user must belong to one group that matches
  16. reqGroups []string
  17. // The action requested must match one of these strings
  18. actions []string
  19. // The requested target must match this string. The special word SELF is replaced in the pattern by the user's dn before matching
  20. target string
  21. // All attributes requested must match one of these patterns
  22. attributes []string
  23. // All attributes requested must not match any of these patterns
  24. exclAttributes []string
  25. }
  26. func splitNoEmpty(s string) []string {
  27. tmp := strings.Split(s, " ")
  28. ret := []string{}
  29. for _, s := range tmp {
  30. if len(s) > 0 {
  31. ret = append(ret, s)
  32. }
  33. }
  34. return ret
  35. }
  36. func ParseACL(def []string) (ACL, error) {
  37. acl := []ACLEntry{}
  38. for _, item := range def {
  39. parts := strings.Split(item, ":")
  40. if len(parts) != 5 {
  41. return nil, fmt.Errorf("Invalid ACL entry: %s", item)
  42. }
  43. attr, exclAttr := []string{}, []string{}
  44. for _, s := range splitNoEmpty(parts[4]) {
  45. if s[0] == '!' {
  46. exclAttr = append(exclAttr, s[1:])
  47. } else {
  48. attr = append(attr, s)
  49. }
  50. }
  51. item_def := ACLEntry{
  52. user: parts[0],
  53. reqGroups: splitNoEmpty(parts[1]),
  54. actions: splitNoEmpty(parts[2]),
  55. target: parts[3],
  56. attributes: attr,
  57. exclAttributes: exclAttr,
  58. }
  59. acl = append(acl, item_def)
  60. }
  61. return acl, nil
  62. }
  63. func (acl ACL) Check(login *Login, action string, target string, attributes []string) bool {
  64. for _, item := range acl {
  65. if item.Check(login, action, target, attributes) {
  66. return true
  67. }
  68. }
  69. return false
  70. }
  71. func (entry *ACLEntry) Check(login *Login, action string, target string, attributes []string) bool {
  72. if !match(entry.user, login.user) {
  73. return false
  74. }
  75. for _, grp := range entry.reqGroups {
  76. if !matchAny(grp, login.groups) {
  77. return false
  78. }
  79. }
  80. matchTarget := match(entry.target, target)
  81. if !matchTarget && len(target) >= len(login.user) {
  82. start := len(target) - len(login.user)
  83. if target[start:] == login.user {
  84. matchTarget = match(entry.target, target[:start]+"SELF")
  85. }
  86. }
  87. if !matchTarget {
  88. return false
  89. }
  90. if !anyMatch(entry.actions, action) {
  91. return false
  92. }
  93. for _, attrib := range attributes {
  94. if !anyMatch(entry.attributes, attrib) {
  95. return false
  96. }
  97. }
  98. for _, exclAttr := range entry.exclAttributes {
  99. if matchAny(exclAttr, attributes) {
  100. return false
  101. }
  102. }
  103. return true
  104. }
  105. func match(pattern string, val string) bool {
  106. rv, err := path.Match(strings.ToLower(pattern), strings.ToLower(val))
  107. return err == nil && rv
  108. }
  109. func matchAny(pattern string, vals []string) bool {
  110. for _, val := range vals {
  111. if match(pattern, val) {
  112. return true
  113. }
  114. }
  115. return false
  116. }
  117. func anyMatch(patterns []string, val string) bool {
  118. for _, pattern := range patterns {
  119. if match(pattern, val) {
  120. return true
  121. }
  122. }
  123. return false
  124. }