policy.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package policy
  2. import (
  3. "github.com/porter-dev/porter/api/types"
  4. )
  5. type RequestAction struct {
  6. Verb types.APIVerb
  7. Resource types.NameOrUInt
  8. }
  9. // HasScopeAccess checks that a user can perform an action (`verb`) against a specific
  10. // resource (`resource+scope`) according to a `policy`.
  11. func HasScopeAccess(
  12. policy []*types.PolicyDocument,
  13. reqScopes map[types.PermissionScope]*RequestAction,
  14. ) bool {
  15. // iterate through policy documents until a match is found
  16. for _, policyDoc := range policy {
  17. // check that policy document is valid for current API server
  18. isValid, matchDocs := populateAndVerifyPolicyDocument(
  19. policyDoc,
  20. types.ScopeHeirarchy,
  21. types.ProjectScope,
  22. types.ReadWriteVerbGroup(),
  23. reqScopes,
  24. nil,
  25. )
  26. if !isValid {
  27. continue
  28. }
  29. for matchScope, matchDoc := range matchDocs {
  30. // for the matching scope, make sure it matches the allowed resources if the
  31. // resource list is explicitly set
  32. if len(matchDoc.Resources) > 0 && reqScopes[matchScope].Verb != types.APIVerbList {
  33. if !isResourceAllowed(matchDoc, reqScopes[matchScope].Resource) {
  34. isValid = false
  35. }
  36. }
  37. // for the matching scope, make sure it matches the allowed verbs
  38. if !isVerbAllowed(matchDoc, reqScopes[matchScope].Verb) {
  39. isValid = false
  40. }
  41. }
  42. if isValid {
  43. return true
  44. }
  45. }
  46. return false
  47. }
  48. func isResourceAllowed(
  49. matchDoc *types.PolicyDocument,
  50. resource types.NameOrUInt,
  51. ) bool {
  52. valid := false
  53. for _, allowedResource := range matchDoc.Resources {
  54. if allowedResource == resource {
  55. valid = true
  56. break
  57. }
  58. }
  59. return valid
  60. }
  61. func isVerbAllowed(
  62. matchDoc *types.PolicyDocument,
  63. verb types.APIVerb,
  64. ) bool {
  65. valid := false
  66. for _, allowedVerb := range matchDoc.Verbs {
  67. if allowedVerb == verb {
  68. valid = true
  69. }
  70. }
  71. return valid
  72. }
  73. // populateAndVerifyPolicyDocument makes sure that the policy document is valid, and populates
  74. // the policy document with values based on the parent permissions. Since we only want to
  75. // iterate through the PolicyDocument once, we also search for a matching doc and return it.
  76. // See test cases for examples.
  77. func populateAndVerifyPolicyDocument(
  78. policyDoc *types.PolicyDocument,
  79. tree types.ScopeTree,
  80. currScope types.PermissionScope,
  81. parentVerbs []types.APIVerb,
  82. reqScopes map[types.PermissionScope]*RequestAction,
  83. currMatchDocs map[types.PermissionScope]*types.PolicyDocument,
  84. ) (ok bool, matchDocs map[types.PermissionScope]*types.PolicyDocument) {
  85. if currMatchDocs == nil {
  86. currMatchDocs = make(map[types.PermissionScope]*types.PolicyDocument)
  87. }
  88. matchDocs = currMatchDocs
  89. currDoc := policyDoc
  90. if policyDoc == nil {
  91. currDoc = &types.PolicyDocument{
  92. Scope: currScope,
  93. // we only set the verbs to the parentVerbs when the policy document is nil
  94. // in the first place. We don't case on res.Verbs being empty, since this
  95. // may be desired.
  96. Verbs: parentVerbs,
  97. }
  98. }
  99. subTree, ok := tree[currDoc.Scope]
  100. if !ok || currDoc.Scope != currScope {
  101. return false, matchDocs
  102. }
  103. processedChildren := 0
  104. for currScope := range subTree {
  105. if _, exists := currDoc.Children[currScope]; exists {
  106. processedChildren++
  107. }
  108. ok, matchDocs = populateAndVerifyPolicyDocument(
  109. currDoc.Children[currScope],
  110. subTree,
  111. currScope,
  112. currDoc.Verbs,
  113. reqScopes,
  114. matchDocs,
  115. )
  116. if !ok {
  117. break
  118. }
  119. }
  120. // make sure all children of the current document were actually processed: if not,
  121. // the policy document is invalid
  122. if processedChildren != len(currDoc.Children) {
  123. return false, matchDocs
  124. }
  125. if _, ok := reqScopes[currScope]; ok && currDoc.Scope == currScope {
  126. matchDocs[currScope] = currDoc
  127. }
  128. return ok, matchDocs
  129. }