/engine/api/router_middleware_auth_test.go

https://github.com/ovh/cds · Go · 223 lines · 182 code · 36 blank · 5 comment · 0 complexity · b5db4e422d405b960164febf66820d66 MD5 · raw file

  1. package api
  2. import (
  3. "context"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. "github.com/ovh/cds/engine/api/authentication"
  11. "github.com/ovh/cds/engine/api/authentication/builtin"
  12. "github.com/ovh/cds/engine/api/test/assets"
  13. "github.com/ovh/cds/engine/service"
  14. "github.com/ovh/cds/sdk"
  15. )
  16. func Test_authMiddleware_WithAuth(t *testing.T) {
  17. api, db, _ := newTestAPI(t)
  18. u, jwt := assets.InsertLambdaUser(t, db)
  19. config := &service.HandlerConfig{}
  20. Auth(true)(config)
  21. req := assets.NewRequest(t, http.MethodGet, "", nil)
  22. w := httptest.NewRecorder()
  23. ctx, err := api.authMiddleware(context.TODO(), w, req, config)
  24. assert.Error(t, err, "an error should be returned because no jwt was given and auth is required")
  25. req = assets.NewJWTAuthentifiedRequest(t, jwt, http.MethodGet, "", nil)
  26. w = httptest.NewRecorder()
  27. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  28. assert.NoError(t, err, "no error should be returned because a jwt was given and is valid")
  29. require.NotNil(t, getAPIConsumer(ctx))
  30. assert.Equal(t, u.ID, getAPIConsumer(ctx).AuthentifiedUserID)
  31. req = assets.NewJWTAuthentifiedRequest(t, sdk.RandomString(10), http.MethodGet, "", nil)
  32. w = httptest.NewRecorder()
  33. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  34. assert.Error(t, err, "an error should be returned because a jwt was given but no valid session matching")
  35. }
  36. func Test_authMiddleware_WithAuthConsumerDisabled(t *testing.T) {
  37. api, db, _ := newTestAPI(t)
  38. g := assets.InsertGroup(t, db)
  39. u, _ := assets.InsertLambdaUser(t, db, g)
  40. localConsumer, err := authentication.LoadConsumerByTypeAndUserID(context.TODO(), db, sdk.ConsumerLocal, u.ID, authentication.LoadConsumerOptions.WithAuthentifiedUser)
  41. require.NoError(t, err)
  42. builtinConsumer, _, err := builtin.NewConsumer(context.TODO(), db, "builtin", "", localConsumer, []int64{g.ID},
  43. sdk.NewAuthConsumerScopeDetails(sdk.AuthConsumerScopes...))
  44. require.NoError(t, err)
  45. builtinSession, err := authentication.NewSession(context.TODO(), db, builtinConsumer, time.Second*5, false)
  46. require.NoError(t, err)
  47. jwt, err := authentication.NewSessionJWT(builtinSession)
  48. require.NoError(t, err)
  49. config := &service.HandlerConfig{}
  50. Auth(true)(config)
  51. req := assets.NewJWTAuthentifiedRequest(t, jwt, http.MethodGet, "", nil)
  52. w := httptest.NewRecorder()
  53. _, err = api.authMiddleware(context.TODO(), w, req, config)
  54. assert.NoError(t, err, "no error should be returned because a valid jwt was given")
  55. require.NoError(t, authentication.ConsumerRemoveGroup(context.TODO(), db, g))
  56. req = assets.NewJWTAuthentifiedRequest(t, jwt, http.MethodGet, "", nil)
  57. w = httptest.NewRecorder()
  58. _, err = api.authMiddleware(context.TODO(), w, req, config)
  59. assert.Error(t, err, "an error should be returned because the consumer should have been disabled")
  60. }
  61. func Test_authMiddleware_WithoutAuth(t *testing.T) {
  62. api, db, _ := newTestAPI(t)
  63. u, jwt := assets.InsertLambdaUser(t, db)
  64. config := &service.HandlerConfig{}
  65. Auth(false)(config)
  66. req := assets.NewRequest(t, http.MethodGet, "", nil)
  67. w := httptest.NewRecorder()
  68. ctx, err := api.authMiddleware(context.TODO(), w, req, config)
  69. assert.NoError(t, err, "no error should be returned because no jwt was given and auth not required")
  70. assert.Nil(t, getAPIConsumer(ctx))
  71. req = assets.NewJWTAuthentifiedRequest(t, jwt, http.MethodGet, "", nil)
  72. w = httptest.NewRecorder()
  73. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  74. assert.NoError(t, err, "no error should be returned because a jwt was given and is valid")
  75. require.NotNil(t, getAPIConsumer(ctx))
  76. assert.Equal(t, u.ID, getAPIConsumer(ctx).AuthentifiedUserID)
  77. req = assets.NewJWTAuthentifiedRequest(t, sdk.RandomString(10), http.MethodGet, "", nil)
  78. w = httptest.NewRecorder()
  79. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  80. assert.NoError(t, err, "no error should be returned for an invalid jwt when auth is not required")
  81. assert.Nil(t, getAPIConsumer(ctx))
  82. }
  83. func Test_authMiddleware_NeedAdmin(t *testing.T) {
  84. api, db, _ := newTestAPI(t)
  85. _, jwtLambda := assets.InsertLambdaUser(t, db)
  86. admin, jwtAdmin := assets.InsertAdminUser(t, db)
  87. config := &service.HandlerConfig{}
  88. NeedAdmin(true)(config)
  89. req := assets.NewRequest(t, http.MethodGet, "", nil)
  90. w := httptest.NewRecorder()
  91. ctx, err := api.authMiddleware(context.TODO(), w, req, config)
  92. assert.Error(t, err, "an error should be returned because no jwt was given and admin auth is required")
  93. req = assets.NewJWTAuthentifiedRequest(t, jwtLambda, http.MethodGet, "", nil)
  94. w = httptest.NewRecorder()
  95. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  96. assert.Error(t, err, "an error should be returned because a jwt was given for a lambda user")
  97. req = assets.NewJWTAuthentifiedRequest(t, jwtAdmin, http.MethodGet, "", nil)
  98. w = httptest.NewRecorder()
  99. ctx, err = api.authMiddleware(context.TODO(), w, req, config)
  100. assert.NoError(t, err, "no error should be returned because a jwt was given for an admin user")
  101. require.NotNil(t, getAPIConsumer(ctx))
  102. assert.Equal(t, admin.ID, getAPIConsumer(ctx).AuthentifiedUserID)
  103. }
  104. func Test_authMiddleware_WithAuthConsumerScoped(t *testing.T) {
  105. api, db, _ := newTestAPI(t)
  106. g := assets.InsertGroup(t, db)
  107. u, _ := assets.InsertLambdaUser(t, db, g)
  108. localConsumer, err := authentication.LoadConsumerByTypeAndUserID(context.TODO(), db, sdk.ConsumerLocal, u.ID, authentication.LoadConsumerOptions.WithAuthentifiedUser)
  109. require.NoError(t, err)
  110. builtinConsumer, _, err := builtin.NewConsumer(context.TODO(), db, "builtin", "", localConsumer, []int64{g.ID}, []sdk.AuthConsumerScopeDetail{
  111. {
  112. Scope: sdk.AuthConsumerScopeAction,
  113. Endpoints: sdk.AuthConsumerScopeEndpoints{
  114. {
  115. Route: "/my-handler2",
  116. Methods: []string{http.MethodGet},
  117. },
  118. {
  119. Route: "/my-handler3",
  120. },
  121. },
  122. },
  123. {
  124. Scope: sdk.AuthConsumerScopeAdmin,
  125. },
  126. })
  127. require.NoError(t, err)
  128. builtinSession, err := authentication.NewSession(context.TODO(), db, builtinConsumer, time.Second*5, false)
  129. require.NoError(t, err)
  130. jwt, err := authentication.NewSessionJWT(builtinSession)
  131. require.NoError(t, err)
  132. // GET /my-handler1 is forbidden (scope AccessToken required)
  133. configHandler1 := &service.HandlerConfig{
  134. CleanURL: "/my-handler1",
  135. AllowedScopes: []sdk.AuthConsumerScope{sdk.AuthConsumerScopeAccessToken},
  136. Method: http.MethodGet,
  137. }
  138. Auth(true)(configHandler1)
  139. req := assets.NewJWTAuthentifiedRequest(t, jwt, configHandler1.Method, configHandler1.CleanURL, nil)
  140. w := httptest.NewRecorder()
  141. _, err = api.authMiddleware(context.TODO(), w, req, configHandler1)
  142. assert.Error(t, err, "an error should be returned because consumer can't do GET on /my-handler1 missing scope")
  143. // GET /my-handler2 is authorized
  144. configHandler2 := &service.HandlerConfig{
  145. CleanURL: "/my-handler2",
  146. AllowedScopes: []sdk.AuthConsumerScope{sdk.AuthConsumerScopeAction},
  147. Method: http.MethodGet,
  148. }
  149. Auth(true)(configHandler2)
  150. req = assets.NewJWTAuthentifiedRequest(t, jwt, configHandler2.Method, configHandler2.CleanURL, nil)
  151. w = httptest.NewRecorder()
  152. _, err = api.authMiddleware(context.TODO(), w, req, configHandler2)
  153. assert.NoError(t, err, "no error should be returned because consumer can do GET on /my-handler2")
  154. // POST /my-handler2 is forbidden (missing POST method in scope Action)
  155. configHandler3 := &service.HandlerConfig{
  156. CleanURL: "/my-handler2",
  157. AllowedScopes: []sdk.AuthConsumerScope{sdk.AuthConsumerScopeAction},
  158. Method: http.MethodPost,
  159. }
  160. Auth(true)(configHandler3)
  161. req = assets.NewJWTAuthentifiedRequest(t, jwt, configHandler3.Method, configHandler3.CleanURL, nil)
  162. w = httptest.NewRecorder()
  163. _, err = api.authMiddleware(context.TODO(), w, req, configHandler3)
  164. assert.Error(t, err, "an error should be returned because consumer can't do POST on /my-handler2")
  165. // DELETE /my-handler3 is authorized as no method restriction set on route.
  166. configHandler4 := &service.HandlerConfig{
  167. CleanURL: "/my-handler3",
  168. AllowedScopes: []sdk.AuthConsumerScope{sdk.AuthConsumerScopeAction},
  169. Method: http.MethodDelete,
  170. }
  171. Auth(true)(configHandler4)
  172. req = assets.NewJWTAuthentifiedRequest(t, jwt, configHandler4.Method, configHandler4.CleanURL, nil)
  173. w = httptest.NewRecorder()
  174. _, err = api.authMiddleware(context.TODO(), w, req, configHandler4)
  175. assert.NoError(t, err, "no error should be returned because consumer can do any methods on /my-handler3")
  176. // PUT /my-handler4 is authorized as no route restriction set on scope.
  177. configHandler5 := &service.HandlerConfig{
  178. CleanURL: "/my-handler4",
  179. AllowedScopes: []sdk.AuthConsumerScope{sdk.AuthConsumerScopeAdmin},
  180. Method: http.MethodPut,
  181. }
  182. Auth(true)(configHandler5)
  183. req = assets.NewJWTAuthentifiedRequest(t, jwt, configHandler5.Method, configHandler5.CleanURL, nil)
  184. w = httptest.NewRecorder()
  185. _, err = api.authMiddleware(context.TODO(), w, req, configHandler5)
  186. assert.NoError(t, err, "no error should be returned because consumer can access any routes for scope Admin")
  187. }