route_test.go 10 KB

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