route_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package lfs
  5. import (
  6. "context"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/http/httptest"
  11. "testing"
  12. "github.com/stretchr/testify/assert"
  13. "gopkg.in/macaron.v1"
  14. "gogs.io/gogs/internal/auth"
  15. "gogs.io/gogs/internal/database"
  16. "gogs.io/gogs/internal/lfsutil"
  17. )
  18. func TestAuthenticate(t *testing.T) {
  19. tests := []struct {
  20. name string
  21. header http.Header
  22. mockUsersStore func() database.UsersStore
  23. mockStore func() *MockStore
  24. expStatusCode int
  25. expHeader http.Header
  26. expBody string
  27. }{
  28. {
  29. name: "no authorization",
  30. expStatusCode: http.StatusUnauthorized,
  31. expHeader: http.Header{
  32. "Lfs-Authenticate": []string{`Basic realm="Git LFS"`},
  33. "Content-Type": []string{"application/vnd.git-lfs+json"},
  34. },
  35. expBody: `{"message":"Credentials needed"}` + "\n",
  36. },
  37. {
  38. name: "user has 2FA enabled",
  39. header: http.Header{
  40. "Authorization": []string{"Basic dXNlcm5hbWU6cGFzc3dvcmQ="},
  41. },
  42. mockUsersStore: func() database.UsersStore {
  43. mock := NewMockUsersStore()
  44. mock.AuthenticateFunc.SetDefaultReturn(&database.User{}, nil)
  45. return mock
  46. },
  47. mockStore: func() *MockStore {
  48. mockStore := NewMockStore()
  49. mockStore.IsTwoFactorEnabledFunc.SetDefaultReturn(true)
  50. return mockStore
  51. },
  52. expStatusCode: http.StatusBadRequest,
  53. expHeader: http.Header{},
  54. expBody: "Users with 2FA enabled are not allowed to authenticate via username and password.",
  55. },
  56. {
  57. name: "both user and access token do not exist",
  58. header: http.Header{
  59. "Authorization": []string{"Basic dXNlcm5hbWU="},
  60. },
  61. mockUsersStore: func() database.UsersStore {
  62. mock := NewMockUsersStore()
  63. mock.AuthenticateFunc.SetDefaultReturn(nil, auth.ErrBadCredentials{})
  64. return mock
  65. },
  66. mockStore: func() *MockStore {
  67. mockStore := NewMockStore()
  68. mockStore.GetAccessTokenBySHA1Func.SetDefaultReturn(nil, database.ErrAccessTokenNotExist{})
  69. return mockStore
  70. },
  71. expStatusCode: http.StatusUnauthorized,
  72. expHeader: http.Header{
  73. "Lfs-Authenticate": []string{`Basic realm="Git LFS"`},
  74. "Content-Type": []string{"application/vnd.git-lfs+json"},
  75. },
  76. expBody: `{"message":"Credentials needed"}` + "\n",
  77. },
  78. {
  79. name: "authenticated by username and password",
  80. header: http.Header{
  81. "Authorization": []string{"Basic dXNlcm5hbWU6cGFzc3dvcmQ="},
  82. },
  83. mockUsersStore: func() database.UsersStore {
  84. mock := NewMockUsersStore()
  85. mock.AuthenticateFunc.SetDefaultReturn(&database.User{ID: 1, Name: "unknwon"}, nil)
  86. return mock
  87. },
  88. mockStore: func() *MockStore {
  89. mockStore := NewMockStore()
  90. mockStore.IsTwoFactorEnabledFunc.SetDefaultReturn(false)
  91. return mockStore
  92. },
  93. expStatusCode: http.StatusOK,
  94. expHeader: http.Header{},
  95. expBody: "ID: 1, Name: unknwon",
  96. },
  97. {
  98. name: "authenticate by access token via username",
  99. header: http.Header{
  100. "Authorization": []string{"Basic dXNlcm5hbWU="},
  101. },
  102. mockUsersStore: func() database.UsersStore {
  103. mock := NewMockUsersStore()
  104. mock.AuthenticateFunc.SetDefaultReturn(nil, auth.ErrBadCredentials{})
  105. mock.GetByIDFunc.SetDefaultReturn(&database.User{ID: 1, Name: "unknwon"}, nil)
  106. return mock
  107. },
  108. mockStore: func() *MockStore {
  109. mockStore := NewMockStore()
  110. mockStore.GetAccessTokenBySHA1Func.SetDefaultReturn(&database.AccessToken{}, nil)
  111. return mockStore
  112. },
  113. expStatusCode: http.StatusOK,
  114. expHeader: http.Header{},
  115. expBody: "ID: 1, Name: unknwon",
  116. },
  117. {
  118. name: "authenticate by access token via password",
  119. header: http.Header{
  120. "Authorization": []string{"Basic dXNlcm5hbWU6cGFzc3dvcmQ="},
  121. },
  122. mockUsersStore: func() database.UsersStore {
  123. mock := NewMockUsersStore()
  124. mock.AuthenticateFunc.SetDefaultReturn(nil, auth.ErrBadCredentials{})
  125. mock.GetByIDFunc.SetDefaultReturn(&database.User{ID: 1, Name: "unknwon"}, nil)
  126. return mock
  127. },
  128. mockStore: func() *MockStore {
  129. mockStore := NewMockStore()
  130. mockStore.GetAccessTokenBySHA1Func.SetDefaultHook(func(_ context.Context, sha1 string) (*database.AccessToken, error) {
  131. if sha1 == "password" {
  132. return &database.AccessToken{}, nil
  133. }
  134. return nil, database.ErrAccessTokenNotExist{}
  135. })
  136. return mockStore
  137. },
  138. expStatusCode: http.StatusOK,
  139. expHeader: http.Header{},
  140. expBody: "ID: 1, Name: unknwon",
  141. },
  142. }
  143. for _, test := range tests {
  144. t.Run(test.name, func(t *testing.T) {
  145. if test.mockUsersStore != nil {
  146. database.SetMockUsersStore(t, test.mockUsersStore())
  147. }
  148. if test.mockStore == nil {
  149. test.mockStore = NewMockStore
  150. }
  151. m := macaron.New()
  152. m.Use(macaron.Renderer())
  153. m.Get("/", authenticate(test.mockStore()), func(w http.ResponseWriter, user *database.User) {
  154. _, _ = fmt.Fprintf(w, "ID: %d, Name: %s", user.ID, user.Name)
  155. })
  156. r, err := http.NewRequest("GET", "/", nil)
  157. if err != nil {
  158. t.Fatal(err)
  159. }
  160. r.Header = test.header
  161. rr := httptest.NewRecorder()
  162. m.ServeHTTP(rr, r)
  163. resp := rr.Result()
  164. assert.Equal(t, test.expStatusCode, resp.StatusCode)
  165. assert.Equal(t, test.expHeader, resp.Header)
  166. body, err := io.ReadAll(resp.Body)
  167. if err != nil {
  168. t.Fatal(err)
  169. }
  170. assert.Equal(t, test.expBody, string(body))
  171. })
  172. }
  173. }
  174. func TestAuthorize(t *testing.T) {
  175. tests := []struct {
  176. name string
  177. accessMode database.AccessMode
  178. mockUsersStore func() database.UsersStore
  179. mockStore func() *MockStore
  180. expStatusCode int
  181. expBody string
  182. }{
  183. {
  184. name: "user does not exist",
  185. accessMode: database.AccessModeNone,
  186. mockUsersStore: func() database.UsersStore {
  187. mock := NewMockUsersStore()
  188. mock.GetByUsernameFunc.SetDefaultReturn(nil, database.ErrUserNotExist{})
  189. return mock
  190. },
  191. expStatusCode: http.StatusNotFound,
  192. },
  193. {
  194. name: "repository does not exist",
  195. accessMode: database.AccessModeNone,
  196. mockUsersStore: func() database.UsersStore {
  197. mock := NewMockUsersStore()
  198. mock.GetByUsernameFunc.SetDefaultHook(func(ctx context.Context, username string) (*database.User, error) {
  199. return &database.User{Name: username}, nil
  200. })
  201. return mock
  202. },
  203. mockStore: func() *MockStore {
  204. mockStore := NewMockStore()
  205. mockStore.GetRepositoryByNameFunc.SetDefaultReturn(nil, database.ErrRepoNotExist{})
  206. return mockStore
  207. },
  208. expStatusCode: http.StatusNotFound,
  209. },
  210. {
  211. name: "actor is not authorized",
  212. accessMode: database.AccessModeWrite,
  213. mockUsersStore: func() database.UsersStore {
  214. mock := NewMockUsersStore()
  215. mock.GetByUsernameFunc.SetDefaultHook(func(ctx context.Context, username string) (*database.User, error) {
  216. return &database.User{Name: username}, nil
  217. })
  218. return mock
  219. },
  220. mockStore: func() *MockStore {
  221. mockStore := NewMockStore()
  222. mockStore.AuthorizeRepositoryAccessFunc.SetDefaultHook(func(_ context.Context, _ int64, _ int64, desired database.AccessMode, _ database.AccessModeOptions) bool {
  223. return desired <= database.AccessModeRead
  224. })
  225. mockStore.GetRepositoryByNameFunc.SetDefaultHook(func(ctx context.Context, ownerID int64, name string) (*database.Repository, error) {
  226. return &database.Repository{Name: name}, nil
  227. })
  228. return mockStore
  229. },
  230. expStatusCode: http.StatusNotFound,
  231. },
  232. {
  233. name: "actor is authorized",
  234. accessMode: database.AccessModeRead,
  235. mockUsersStore: func() database.UsersStore {
  236. mock := NewMockUsersStore()
  237. mock.GetByUsernameFunc.SetDefaultHook(func(ctx context.Context, username string) (*database.User, error) {
  238. return &database.User{Name: username}, nil
  239. })
  240. return mock
  241. },
  242. mockStore: func() *MockStore {
  243. mockStore := NewMockStore()
  244. mockStore.AuthorizeRepositoryAccessFunc.SetDefaultHook(func(_ context.Context, _ int64, _ int64, desired database.AccessMode, _ database.AccessModeOptions) bool {
  245. return desired <= database.AccessModeRead
  246. })
  247. mockStore.GetRepositoryByNameFunc.SetDefaultHook(func(ctx context.Context, ownerID int64, name string) (*database.Repository, error) {
  248. return &database.Repository{Name: name}, nil
  249. })
  250. return mockStore
  251. },
  252. expStatusCode: http.StatusOK,
  253. expBody: "owner.Name: owner, repo.Name: repo",
  254. },
  255. }
  256. for _, test := range tests {
  257. t.Run(test.name, func(t *testing.T) {
  258. if test.mockUsersStore != nil {
  259. database.SetMockUsersStore(t, test.mockUsersStore())
  260. }
  261. mockStore := NewMockStore()
  262. if test.mockStore != nil {
  263. mockStore = test.mockStore()
  264. }
  265. m := macaron.New()
  266. m.Use(macaron.Renderer())
  267. m.Use(func(c *macaron.Context) {
  268. c.Map(&database.User{})
  269. })
  270. m.Get(
  271. "/:username/:reponame",
  272. authorize(mockStore, test.accessMode),
  273. func(w http.ResponseWriter, owner *database.User, repo *database.Repository) {
  274. _, _ = fmt.Fprintf(w, "owner.Name: %s, repo.Name: %s", owner.Name, repo.Name)
  275. },
  276. )
  277. r, err := http.NewRequest("GET", "/owner/repo", nil)
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. rr := httptest.NewRecorder()
  282. m.ServeHTTP(rr, r)
  283. resp := rr.Result()
  284. assert.Equal(t, test.expStatusCode, resp.StatusCode)
  285. body, err := io.ReadAll(resp.Body)
  286. if err != nil {
  287. t.Fatal(err)
  288. }
  289. assert.Equal(t, test.expBody, string(body))
  290. })
  291. }
  292. }
  293. func Test_verifyHeader(t *testing.T) {
  294. tests := []struct {
  295. name string
  296. verifyHeader macaron.Handler
  297. header http.Header
  298. expStatusCode int
  299. }{
  300. {
  301. name: "header not found",
  302. verifyHeader: verifyHeader("Accept", contentType, http.StatusNotAcceptable),
  303. expStatusCode: http.StatusNotAcceptable,
  304. },
  305. {
  306. name: "header found",
  307. verifyHeader: verifyHeader("Accept", "application/vnd.git-lfs+json", http.StatusNotAcceptable),
  308. header: http.Header{
  309. "Accept": []string{"application/vnd.git-lfs+json; charset=utf-8"},
  310. },
  311. expStatusCode: http.StatusOK,
  312. },
  313. }
  314. for _, test := range tests {
  315. t.Run(test.name, func(t *testing.T) {
  316. m := macaron.New()
  317. m.Use(macaron.Renderer())
  318. m.Get("/", test.verifyHeader)
  319. r, err := http.NewRequest("GET", "/", nil)
  320. if err != nil {
  321. t.Fatal(err)
  322. }
  323. r.Header = test.header
  324. rr := httptest.NewRecorder()
  325. m.ServeHTTP(rr, r)
  326. resp := rr.Result()
  327. assert.Equal(t, test.expStatusCode, resp.StatusCode)
  328. })
  329. }
  330. }
  331. func Test_verifyOID(t *testing.T) {
  332. m := macaron.New()
  333. m.Get("/:oid", verifyOID(), func(w http.ResponseWriter, oid lfsutil.OID) {
  334. fmt.Fprintf(w, "oid: %s", oid)
  335. })
  336. tests := []struct {
  337. name string
  338. url string
  339. expStatusCode int
  340. expBody string
  341. }{
  342. {
  343. name: "bad oid",
  344. url: "/bad_oid",
  345. expStatusCode: http.StatusBadRequest,
  346. expBody: `{"message":"Invalid oid"}` + "\n",
  347. },
  348. {
  349. name: "good oid",
  350. url: "/ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
  351. expStatusCode: http.StatusOK,
  352. expBody: "oid: ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
  353. },
  354. }
  355. for _, test := range tests {
  356. t.Run(test.name, func(t *testing.T) {
  357. r, err := http.NewRequest("GET", test.url, nil)
  358. if err != nil {
  359. t.Fatal(err)
  360. }
  361. rr := httptest.NewRecorder()
  362. m.ServeHTTP(rr, r)
  363. resp := rr.Result()
  364. assert.Equal(t, test.expStatusCode, resp.StatusCode)
  365. body, err := io.ReadAll(resp.Body)
  366. if err != nil {
  367. t.Fatal(err)
  368. }
  369. assert.Equal(t, test.expBody, string(body))
  370. })
  371. }
  372. }
  373. func Test_internalServerError(t *testing.T) {
  374. rr := httptest.NewRecorder()
  375. internalServerError(rr)
  376. resp := rr.Result()
  377. assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
  378. body, err := io.ReadAll(resp.Body)
  379. if err != nil {
  380. t.Fatal(err)
  381. }
  382. assert.Equal(t, `{"message":"Internal server error"}`+"\n", string(body))
  383. }