policy.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package authz
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/porter-dev/porter/api/server/authz/policy"
  7. "github.com/porter-dev/porter/api/server/shared"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/api/types"
  10. "github.com/porter-dev/porter/internal/models"
  11. )
  12. type PolicyMiddleware struct {
  13. config *shared.Config
  14. endpointMeta types.APIRequestMetadata
  15. loader policy.PolicyDocumentLoader
  16. }
  17. func NewPolicyMiddleware(
  18. config *shared.Config,
  19. endpointMeta types.APIRequestMetadata,
  20. loader policy.PolicyDocumentLoader,
  21. ) *PolicyMiddleware {
  22. return &PolicyMiddleware{config, endpointMeta, loader}
  23. }
  24. func (p *PolicyMiddleware) Middleware(next http.Handler) http.Handler {
  25. return &PolicyHandler{next, p.config, p.endpointMeta, p.loader}
  26. }
  27. type PolicyHandler struct {
  28. next http.Handler
  29. config *shared.Config
  30. endpointMeta types.APIRequestMetadata
  31. loader policy.PolicyDocumentLoader
  32. }
  33. func (h *PolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. // get the full map of scopes to resource actions
  35. reqScopes, reqErr := getRequestActionForEndpoint(r, h.endpointMeta)
  36. if reqErr != nil {
  37. apierrors.HandleAPIError(w, h.config.Logger, reqErr)
  38. return
  39. }
  40. // load policy documents for the user + project
  41. projID := reqScopes[types.ProjectScope].Resource.UInt
  42. user, _ := r.Context().Value(types.UserScope).(*models.User)
  43. policyDocs, reqErr := h.loader.LoadPolicyDocuments(user.ID, projID)
  44. if reqErr != nil {
  45. apierrors.HandleAPIError(w, h.config.Logger, reqErr)
  46. return
  47. }
  48. // validate that the policy permits the action
  49. hasAccess := policy.HasScopeAccess(policyDocs, reqScopes)
  50. if !hasAccess {
  51. apierrors.HandleAPIError(
  52. w,
  53. h.config.Logger,
  54. apierrors.NewErrForbidden(fmt.Errorf("policy forbids action for user %d in project %d", user.ID, projID)),
  55. )
  56. return
  57. }
  58. // add the set of resource ids to the request context
  59. ctx := NewRequestScopeCtx(r.Context(), reqScopes)
  60. r = r.WithContext(ctx)
  61. h.next.ServeHTTP(w, r)
  62. }
  63. const RequestScopeCtxKey = "requestscopes"
  64. func NewRequestScopeCtx(ctx context.Context, reqScopes map[types.PermissionScope]*policy.RequestAction) context.Context {
  65. return context.WithValue(ctx, RequestScopeCtxKey, reqScopes)
  66. }
  67. func getRequestActionForEndpoint(
  68. r *http.Request,
  69. endpointMeta types.APIRequestMetadata,
  70. ) (res map[types.PermissionScope]*policy.RequestAction, reqErr apierrors.RequestError) {
  71. res = make(map[types.PermissionScope]*policy.RequestAction)
  72. // iterate through scopes, attach policies as needed
  73. for _, scope := range endpointMeta.Scopes {
  74. // find the resource ID and create the resource
  75. resource := types.NameOrUInt{}
  76. switch scope {
  77. case types.ProjectScope:
  78. resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamProjectID))
  79. case types.ClusterScope:
  80. resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamClusterID))
  81. case types.NamespaceScope:
  82. resource.Name, reqErr = GetURLParamString(r, string(types.URLParamNamespace))
  83. case types.ApplicationScope:
  84. resource.Name, reqErr = GetURLParamString(r, string(types.URLParamApplication))
  85. }
  86. if reqErr != nil {
  87. return nil, reqErr
  88. }
  89. res[scope] = &policy.RequestAction{
  90. Verb: endpointMeta.Verb,
  91. Resource: resource,
  92. }
  93. }
  94. return res, nil
  95. }