policy_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package authz_test
  2. import (
  3. "encoding/json"
  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/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. project, err := config.Repo.Project().CreateProject(&models.Project{
  28. Name: "test-project",
  29. })
  30. if err != nil {
  31. t.Fatal(err)
  32. }
  33. policyBytes, err := json.Marshal(types.AdminPolicy)
  34. if err != nil {
  35. t.Fatalf("%v", err)
  36. }
  37. pol, err := config.Repo.Policy().CreatePolicy(&models.Policy{
  38. UniqueID: "test-policy-uid",
  39. ProjectID: project.ID,
  40. Name: "test-policy",
  41. PolicyBytes: policyBytes,
  42. })
  43. if err != nil {
  44. t.Fatalf("%v", err)
  45. }
  46. role, err := config.Repo.ProjectRole().CreateProjectRole(&models.ProjectRole{
  47. UniqueID: "1-admin",
  48. ProjectID: project.ID,
  49. PolicyUID: pol.UniqueID,
  50. Name: "admin",
  51. })
  52. if err != nil {
  53. t.Fatalf("%v", err)
  54. }
  55. err = config.Repo.ProjectRole().UpdateUsersInProjectRole(project.ID, role.UniqueID, []uint{user.ID})
  56. if err != nil {
  57. t.Fatalf("%v", err)
  58. }
  59. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  60. req = apitest.WithURLParams(t, req, map[string]string{
  61. "project_id": "1",
  62. "cluster_id": "1",
  63. })
  64. req = apitest.WithAuthenticatedUser(t, req, user)
  65. handler.ServeHTTP(rr, req)
  66. assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
  67. types.ProjectScope: {
  68. Verb: types.APIVerbCreate,
  69. Resource: types.NameOrUInt{
  70. UInt: 1,
  71. },
  72. },
  73. types.ClusterScope: {
  74. Verb: types.APIVerbCreate,
  75. Resource: types.NameOrUInt{
  76. UInt: 1,
  77. },
  78. },
  79. })
  80. }
  81. func TestPolicyMiddlewareSuccessfulApplication(t *testing.T) {
  82. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  83. Verb: types.APIVerbCreate,
  84. Method: types.HTTPVerbPost,
  85. Scopes: []types.PermissionScope{
  86. types.ProjectScope,
  87. types.ClusterScope,
  88. types.NamespaceScope,
  89. types.ReleaseScope,
  90. },
  91. }, false, false)
  92. user := apitest.CreateTestUser(t, config, true)
  93. project, err := config.Repo.Project().CreateProject(&models.Project{
  94. Name: "test-project",
  95. })
  96. if err != nil {
  97. t.Fatal(err)
  98. }
  99. policyBytes, err := json.Marshal(types.AdminPolicy)
  100. if err != nil {
  101. t.Fatalf("%v", err)
  102. }
  103. pol, err := config.Repo.Policy().CreatePolicy(&models.Policy{
  104. UniqueID: "test-policy-uid",
  105. ProjectID: project.ID,
  106. Name: "test-policy",
  107. PolicyBytes: policyBytes,
  108. })
  109. if err != nil {
  110. t.Fatalf("%v", err)
  111. }
  112. role, err := config.Repo.ProjectRole().CreateProjectRole(&models.ProjectRole{
  113. UniqueID: "1-admin",
  114. ProjectID: project.ID,
  115. PolicyUID: pol.UniqueID,
  116. Name: "admin",
  117. })
  118. if err != nil {
  119. t.Fatalf("%v", err)
  120. }
  121. err = config.Repo.ProjectRole().UpdateUsersInProjectRole(project.ID, role.UniqueID, []uint{user.ID})
  122. if err != nil {
  123. t.Fatalf("%v", err)
  124. }
  125. req, rr := apitest.GetRequestAndRecorder(
  126. t,
  127. string(types.HTTPVerbPost),
  128. "/api/projects/1/clusters/1/default/app-1",
  129. nil,
  130. )
  131. req = apitest.WithURLParams(t, req, map[string]string{
  132. "project_id": "1",
  133. "cluster_id": "1",
  134. "namespace": "default",
  135. "name": "app-1",
  136. })
  137. req = apitest.WithAuthenticatedUser(t, req, user)
  138. handler.ServeHTTP(rr, req)
  139. assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
  140. types.ProjectScope: {
  141. Verb: types.APIVerbCreate,
  142. Resource: types.NameOrUInt{
  143. UInt: 1,
  144. },
  145. },
  146. types.ClusterScope: {
  147. Verb: types.APIVerbCreate,
  148. Resource: types.NameOrUInt{
  149. UInt: 1,
  150. },
  151. },
  152. types.NamespaceScope: {
  153. Verb: types.APIVerbCreate,
  154. Resource: types.NameOrUInt{
  155. Name: "default",
  156. },
  157. },
  158. types.ReleaseScope: {
  159. Verb: types.APIVerbCreate,
  160. Resource: types.NameOrUInt{
  161. Name: "app-1",
  162. },
  163. },
  164. })
  165. }
  166. func TestPolicyMiddlewareInvalidPermissions(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. }, false, true)
  175. user := apitest.CreateTestUser(t, config, true)
  176. _, err := config.Repo.Project().CreateProject(&models.Project{
  177. Name: "test-project",
  178. })
  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": "1",
  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.AssertResponseForbidden(t, rr)
  191. }
  192. func TestPolicyMiddlewareFailInvalidLoader(t *testing.T) {
  193. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  194. Verb: types.APIVerbCreate,
  195. Method: types.HTTPVerbPost,
  196. Scopes: []types.PermissionScope{
  197. types.ProjectScope,
  198. types.ClusterScope,
  199. },
  200. }, true, false)
  201. user := apitest.CreateTestUser(t, config, true)
  202. _, err := config.Repo.Project().CreateProject(&models.Project{
  203. Name: "test-project",
  204. })
  205. if err != nil {
  206. t.Fatal(err)
  207. }
  208. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  209. req = apitest.WithURLParams(t, req, map[string]string{
  210. "project_id": "1",
  211. "cluster_id": "1",
  212. })
  213. req = apitest.WithAuthenticatedUser(t, req, user)
  214. handler.ServeHTTP(rr, req)
  215. assertInternalError(t, next, rr)
  216. }
  217. func TestPolicyMiddlewareFailBadParam(t *testing.T) {
  218. config, handler, next := loadHandlers(t, types.APIRequestMetadata{
  219. Verb: types.APIVerbCreate,
  220. Method: types.HTTPVerbPost,
  221. Scopes: []types.PermissionScope{
  222. types.ProjectScope,
  223. types.ClusterScope,
  224. },
  225. }, true, false)
  226. user := apitest.CreateTestUser(t, config, true)
  227. _, err := config.Repo.Project().CreateProject(&models.Project{
  228. Name: "test-project",
  229. })
  230. if err != nil {
  231. t.Fatal(err)
  232. }
  233. req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1/clusters/1", nil)
  234. req = apitest.WithURLParams(t, req, map[string]string{
  235. "project_id": "notuint",
  236. "cluster_id": "1",
  237. })
  238. req = apitest.WithAuthenticatedUser(t, req, user)
  239. handler.ServeHTTP(rr, req)
  240. assert.False(t, next.WasCalled, "next handler should not have been called")
  241. apitest.AssertResponseError(t, rr, http.StatusBadRequest, &types.ExternalError{
  242. Error: fmt.Sprintf("could not convert url parameter %s to uint, got %s", "project_id", "notuint"),
  243. })
  244. }
  245. func loadHandlers(
  246. t *testing.T,
  247. endpointMeta types.APIRequestMetadata,
  248. shouldLoaderFail bool,
  249. shouldLoaderLoadViewer bool,
  250. ) (*config.Config, http.Handler, *testHandler) {
  251. config := apitest.LoadConfig(t)
  252. var loader policy.PolicyDocumentLoader = policy.NewBasicPolicyDocumentLoader(config.Repo.ProjectRole(), config.Repo.Policy())
  253. if shouldLoaderFail {
  254. loader = &failingDocLoader{}
  255. }
  256. if shouldLoaderLoadViewer {
  257. loader = &viewerDocLoader{}
  258. }
  259. mwFactory := authz.NewPolicyMiddleware(config, endpointMeta, loader)
  260. next := &testHandler{}
  261. handler := mwFactory.Middleware(next)
  262. return config, handler, next
  263. }
  264. type failingDocLoader struct{}
  265. func (f *failingDocLoader) LoadPolicyDocuments(opts *policy.PolicyLoaderOpts) ([]*types.PolicyDocument, apierrors.RequestError) {
  266. return nil, apierrors.NewErrInternal(fmt.Errorf("new error internal"))
  267. }
  268. type viewerDocLoader struct{}
  269. func (f *viewerDocLoader) LoadPolicyDocuments(opts *policy.PolicyLoaderOpts) ([]*types.PolicyDocument, apierrors.RequestError) {
  270. return types.ViewerPolicy, nil
  271. }
  272. type testHandler struct {
  273. WasCalled bool
  274. ReqScopes map[types.PermissionScope]*types.RequestAction
  275. }
  276. func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  277. t.WasCalled = true
  278. t.ReqScopes, _ = r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  279. }
  280. func assertNextHandlerCalled(
  281. t *testing.T,
  282. next *testHandler,
  283. rr *httptest.ResponseRecorder,
  284. expScopes map[types.PermissionScope]*types.RequestAction,
  285. ) {
  286. // make sure the handler was called with the expected user, and resulted in 200 OK
  287. assert := assert.New(t)
  288. assert.True(next.WasCalled, "next handler should have been called")
  289. assert.Equal(expScopes, next.ReqScopes, "expected scopes should be equal")
  290. assert.Equal(http.StatusOK, rr.Result().StatusCode, "status code should be ok")
  291. }
  292. func assertInternalError(t *testing.T, next *testHandler, rr *httptest.ResponseRecorder) {
  293. assert := assert.New(t)
  294. // first assert that that the next middleware was not called
  295. assert.False(next.WasCalled, "next handler should not have been called")
  296. apitest.AssertResponseInternalServerError(t, rr)
  297. }