policy_test.go 7.9 KB

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