route_test.go 10 KB

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