policy_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package authz_test
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "github.com/porter-dev/porter/api/server/authz"
  9. "github.com/porter-dev/porter/api/server/authz/policy"
  10. "github.com/porter-dev/porter/api/server/handlers/project"
  11. "github.com/porter-dev/porter/api/server/shared/apierrors"
  12. "github.com/porter-dev/porter/api/server/shared/apitest"
  13. "github.com/porter-dev/porter/api/server/shared/config"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/models"
  16. "github.com/stretchr/testify/assert"
  17. )
  18. func TestPolicyMiddlewareSuccessfulProjectCluster(t *testing.T) {
  19. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  20. Verb: types.APIVerbCreate,
  21. Method: types.HTTPVerbPost,
  22. Scopes: []types.PermissionScope{
  23. types.ProjectScope,
  24. types.ClusterScope,
  25. },
  26. }, false, false)
  27. user := apitest.CreateTestUser(t, config, true)
  28. _, _, err := project.CreateProjectWithUser(context.Background(), config.Repo.Project(), &models.Project{
  29. Name: "test-project",
  30. }, user)
  31. if err != nil {
  32. t.Fatal(err)
  33. }
  34. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  35. req = apitest.WithURLParams(t, req, map[string]string{
  36. "project_id": "1",
  37. "cluster_id": "1",
  38. })
  39. req = apitest.WithAuthenticatedUser(t, req, user)
  40. handler.ServeHTTP(rr, req)
  41. assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
  42. types.ProjectScope: {
  43. Verb: types.APIVerbCreate,
  44. Resource: types.NameOrUInt{
  45. UInt: 1,
  46. },
  47. },
  48. types.ClusterScope: {
  49. Verb: types.APIVerbCreate,
  50. Resource: types.NameOrUInt{
  51. UInt: 1,
  52. },
  53. },
  54. })
  55. }
  56. func TestPolicyMiddlewareSuccessfulApplication(t *testing.T) {
  57. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  58. Verb: types.APIVerbCreate,
  59. Method: types.HTTPVerbPost,
  60. Scopes: []types.PermissionScope{
  61. types.ProjectScope,
  62. types.ClusterScope,
  63. types.NamespaceScope,
  64. types.ReleaseScope,
  65. },
  66. }, false, false)
  67. user := apitest.CreateTestUser(t, config, true)
  68. _, _, err := project.CreateProjectWithUser(context.Background(), config.Repo.Project(), &models.Project{
  69. Name: "test-project",
  70. }, user)
  71. if err != nil {
  72. t.Fatal(err)
  73. }
  74. req, rr := apitest.GetRequestAndRecorder(
  75. t,
  76. string(types.HTTPVerbPost),
  77. "/api/projects/1/clusters/1/default/app-1",
  78. nil,
  79. )
  80. req = apitest.WithURLParams(t, req, map[string]string{
  81. "project_id": "1",
  82. "cluster_id": "1",
  83. "namespace": "default",
  84. "name": "app-1",
  85. })
  86. req = apitest.WithAuthenticatedUser(t, req, user)
  87. handler.ServeHTTP(rr, req)
  88. assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
  89. types.ProjectScope: {
  90. Verb: types.APIVerbCreate,
  91. Resource: types.NameOrUInt{
  92. UInt: 1,
  93. },
  94. },
  95. types.ClusterScope: {
  96. Verb: types.APIVerbCreate,
  97. Resource: types.NameOrUInt{
  98. UInt: 1,
  99. },
  100. },
  101. types.NamespaceScope: {
  102. Verb: types.APIVerbCreate,
  103. Resource: types.NameOrUInt{
  104. Name: "default",
  105. },
  106. },
  107. types.ReleaseScope: {
  108. Verb: types.APIVerbCreate,
  109. Resource: types.NameOrUInt{
  110. Name: "app-1",
  111. },
  112. },
  113. })
  114. }
  115. func TestPolicyMiddlewareInvalidPermissions(t *testing.T) {
  116. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  117. Verb: types.APIVerbCreate,
  118. Method: types.HTTPVerbPost,
  119. Scopes: []types.PermissionScope{
  120. types.ProjectScope,
  121. types.ClusterScope,
  122. },
  123. }, false, true)
  124. user := apitest.CreateTestUser(t, config, true)
  125. _, _, err := project.CreateProjectWithUser(context.Background(), config.Repo.Project(), &models.Project{
  126. Name: "test-project",
  127. }, user)
  128. if err != nil {
  129. t.Fatal(err)
  130. }
  131. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  132. req = apitest.WithURLParams(t, req, map[string]string{
  133. "project_id": "1",
  134. "cluster_id": "1",
  135. })
  136. req = apitest.WithAuthenticatedUser(t, req, user)
  137. handler.ServeHTTP(rr, req)
  138. assert.False(t, next.WasCalled, "next handler should not have been called")
  139. apitest.AssertForbiddenError(t, rr)
  140. }
  141. func TestPolicyMiddlewareFailInvalidLoader(t *testing.T) {
  142. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  143. Verb: types.APIVerbCreate,
  144. Method: types.HTTPVerbPost,
  145. Scopes: []types.PermissionScope{
  146. types.ProjectScope,
  147. types.ClusterScope,
  148. },
  149. }, true, false)
  150. user := apitest.CreateTestUser(t, config, true)
  151. _, _, err := project.CreateProjectWithUser(context.Background(), config.Repo.Project(), &models.Project{
  152. Name: "test-project",
  153. }, user)
  154. if err != nil {
  155. t.Fatal(err)
  156. }
  157. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  158. req = apitest.WithURLParams(t, req, map[string]string{
  159. "project_id": "1",
  160. "cluster_id": "1",
  161. })
  162. req = apitest.WithAuthenticatedUser(t, req, user)
  163. handler.ServeHTTP(rr, req)
  164. assertInternalError(t, next, rr)
  165. }
  166. func TestPolicyMiddlewareFailBadParam(t *testing.T) {
  167. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  168. Verb: types.APIVerbCreate,
  169. Method: types.HTTPVerbPost,
  170. Scopes: []types.PermissionScope{
  171. types.ProjectScope,
  172. types.ClusterScope,
  173. },
  174. }, true, false)
  175. user := apitest.CreateTestUser(t, config, true)
  176. _, _, err := project.CreateProjectWithUser(context.Background(), config.Repo.Project(), &models.Project{
  177. Name: "test-project",
  178. }, user)
  179. if err != nil {
  180. t.Fatal(err)
  181. }
  182. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  183. req = apitest.WithURLParams(t, req, map[string]string{
  184. "project_id": "notuint",
  185. "cluster_id": "1",
  186. })
  187. req = apitest.WithAuthenticatedUser(t, req, user)
  188. handler.ServeHTTP(rr, req)
  189. assert.False(t, next.WasCalled, "next handler should not have been called")
  190. apitest.AssertResponseError(t, rr, http.StatusBadRequest, &types.ExternalError{
  191. Error: fmt.Sprintf("could not convert url parameter %s to uint, got %s", "project_id", "notuint"),
  192. })
  193. }
  194. func loadHandlers(
  195. t *testing.T,
  196. endpointMeta types.APIRequestMetadata,
  197. shouldLoaderFail bool,
  198. shouldLoaderLoadViewer bool,
  199. ) (*config.Config, http.Handler, *testHandler) {
  200. config := apitest.LoadConfig(t)
  201. var loader policy.PolicyDocumentLoader = policy.NewBasicPolicyDocumentLoader(config.Repo.Project(), config.Repo.Policy())
  202. if shouldLoaderFail {
  203. loader = &failingDocLoader{}
  204. }
  205. if shouldLoaderLoadViewer {
  206. loader = &viewerDocLoader{}
  207. }
  208. mwFactory := authz.NewPolicyMiddleware(config, endpointMeta, loader)
  209. next := &testHandler{}
  210. handler := mwFactory.Middleware(next)
  211. return config, handler, next
  212. }
  213. type failingDocLoader struct{}
  214. func (f *failingDocLoader) LoadPolicyDocuments(opts *policy.PolicyLoaderOpts) ([]*types.PolicyDocument, apierrors.RequestError) {
  215. return nil, apierrors.NewErrInternal(fmt.Errorf("new error internal"))
  216. }
  217. type viewerDocLoader struct{}
  218. func (f *viewerDocLoader) LoadPolicyDocuments(opts *policy.PolicyLoaderOpts) ([]*types.PolicyDocument, apierrors.RequestError) {
  219. return types.ViewerPolicy, nil
  220. }
  221. type testHandler struct {
  222. WasCalled bool
  223. ReqScopes map[types.PermissionScope]*types.RequestAction
  224. }
  225. func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  226. t.WasCalled = true
  227. t.ReqScopes, _ = r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  228. }
  229. func assertNextHandlerCalled(
  230. t *testing.T,
  231. next *testHandler,
  232. rr *httptest.ResponseRecorder,
  233. expScopes map[types.PermissionScope]*types.RequestAction,
  234. ) {
  235. // make sure the handler was called with the expected user, and resulted in 200 OK
  236. assert := assert.New(t)
  237. assert.True(next.WasCalled, "next handler should have been called")
  238. assert.Equal(expScopes, next.ReqScopes, "expected scopes should be equal")
  239. assert.Equal(http.StatusOK, rr.Result().StatusCode, "status code should be ok")
  240. }
  241. func assertInternalError(t *testing.T, next *testHandler, rr *httptest.ResponseRecorder) {
  242. assert := assert.New(t)
  243. // first assert that that the next middleware was not called
  244. assert.False(next.WasCalled, "next handler should not have been called")
  245. apitest.AssertInternalServerError(t, rr)
  246. }