login_sources_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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 db
  5. import (
  6. "context"
  7. "testing"
  8. "time"
  9. mockrequire "github.com/derision-test/go-mockgen/testutil/require"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "gorm.io/gorm"
  13. "gogs.io/gogs/internal/auth"
  14. "gogs.io/gogs/internal/auth/github"
  15. "gogs.io/gogs/internal/auth/pam"
  16. "gogs.io/gogs/internal/errutil"
  17. )
  18. func TestLoginSource_BeforeSave(t *testing.T) {
  19. now := time.Now()
  20. db := &gorm.DB{
  21. Config: &gorm.Config{
  22. SkipDefaultTransaction: true,
  23. NowFunc: func() time.Time {
  24. return now
  25. },
  26. },
  27. }
  28. t.Run("Config has not been set", func(t *testing.T) {
  29. s := &LoginSource{}
  30. err := s.BeforeSave(db)
  31. require.NoError(t, err)
  32. assert.Empty(t, s.Config)
  33. })
  34. t.Run("Config has been set", func(t *testing.T) {
  35. s := &LoginSource{
  36. Provider: pam.NewProvider(&pam.Config{
  37. ServiceName: "pam_service",
  38. }),
  39. }
  40. err := s.BeforeSave(db)
  41. require.NoError(t, err)
  42. assert.Equal(t, `{"ServiceName":"pam_service"}`, s.Config)
  43. })
  44. }
  45. func TestLoginSource_BeforeCreate(t *testing.T) {
  46. now := time.Now()
  47. db := &gorm.DB{
  48. Config: &gorm.Config{
  49. SkipDefaultTransaction: true,
  50. NowFunc: func() time.Time {
  51. return now
  52. },
  53. },
  54. }
  55. t.Run("CreatedUnix has been set", func(t *testing.T) {
  56. s := &LoginSource{CreatedUnix: 1}
  57. _ = s.BeforeCreate(db)
  58. assert.Equal(t, int64(1), s.CreatedUnix)
  59. assert.Equal(t, int64(0), s.UpdatedUnix)
  60. })
  61. t.Run("CreatedUnix has not been set", func(t *testing.T) {
  62. s := &LoginSource{}
  63. _ = s.BeforeCreate(db)
  64. assert.Equal(t, db.NowFunc().Unix(), s.CreatedUnix)
  65. assert.Equal(t, db.NowFunc().Unix(), s.UpdatedUnix)
  66. })
  67. }
  68. func Test_loginSources(t *testing.T) {
  69. if testing.Short() {
  70. t.Skip()
  71. }
  72. t.Parallel()
  73. tables := []interface{}{new(LoginSource), new(User)}
  74. db := &loginSources{
  75. DB: initTestDB(t, "loginSources", tables...),
  76. }
  77. for _, tc := range []struct {
  78. name string
  79. test func(*testing.T, *loginSources)
  80. }{
  81. {"Create", loginSourcesCreate},
  82. {"Count", loginSourcesCount},
  83. {"DeleteByID", loginSourcesDeleteByID},
  84. {"GetByID", loginSourcesGetByID},
  85. {"List", loginSourcesList},
  86. {"ResetNonDefault", loginSourcesResetNonDefault},
  87. {"Save", loginSourcesSave},
  88. } {
  89. t.Run(tc.name, func(t *testing.T) {
  90. t.Cleanup(func() {
  91. err := clearTables(t, db.DB, tables...)
  92. require.NoError(t, err)
  93. })
  94. tc.test(t, db)
  95. })
  96. if t.Failed() {
  97. break
  98. }
  99. }
  100. }
  101. func loginSourcesCreate(t *testing.T, db *loginSources) {
  102. ctx := context.Background()
  103. // Create first login source with name "GitHub"
  104. source, err := db.Create(ctx,
  105. CreateLoginSourceOpts{
  106. Type: auth.GitHub,
  107. Name: "GitHub",
  108. Activated: true,
  109. Default: false,
  110. Config: &github.Config{
  111. APIEndpoint: "https://api.github.com",
  112. },
  113. },
  114. )
  115. require.NoError(t, err)
  116. // Get it back and check the Created field
  117. source, err = db.GetByID(ctx, source.ID)
  118. require.NoError(t, err)
  119. assert.Equal(t, db.NowFunc().Format(time.RFC3339), source.Created.UTC().Format(time.RFC3339))
  120. assert.Equal(t, db.NowFunc().Format(time.RFC3339), source.Updated.UTC().Format(time.RFC3339))
  121. // Try create second login source with same name should fail
  122. _, err = db.Create(ctx, CreateLoginSourceOpts{Name: source.Name})
  123. wantErr := ErrLoginSourceAlreadyExist{args: errutil.Args{"name": source.Name}}
  124. assert.Equal(t, wantErr, err)
  125. }
  126. func loginSourcesCount(t *testing.T, db *loginSources) {
  127. ctx := context.Background()
  128. // Create two login sources, one in database and one as source file.
  129. _, err := db.Create(ctx,
  130. CreateLoginSourceOpts{
  131. Type: auth.GitHub,
  132. Name: "GitHub",
  133. Activated: true,
  134. Default: false,
  135. Config: &github.Config{
  136. APIEndpoint: "https://api.github.com",
  137. },
  138. },
  139. )
  140. require.NoError(t, err)
  141. mock := NewMockLoginSourceFilesStore()
  142. mock.LenFunc.SetDefaultReturn(2)
  143. setMockLoginSourceFilesStore(t, db, mock)
  144. assert.Equal(t, int64(3), db.Count(ctx))
  145. }
  146. func loginSourcesDeleteByID(t *testing.T, db *loginSources) {
  147. ctx := context.Background()
  148. t.Run("delete but in used", func(t *testing.T) {
  149. source, err := db.Create(ctx,
  150. CreateLoginSourceOpts{
  151. Type: auth.GitHub,
  152. Name: "GitHub",
  153. Activated: true,
  154. Default: false,
  155. Config: &github.Config{
  156. APIEndpoint: "https://api.github.com",
  157. },
  158. },
  159. )
  160. require.NoError(t, err)
  161. // Create a user that uses this login source
  162. _, err = (&users{DB: db.DB}).Create(ctx, "alice", "",
  163. CreateUserOpts{
  164. LoginSource: source.ID,
  165. },
  166. )
  167. require.NoError(t, err)
  168. // Delete the login source will result in error
  169. err = db.DeleteByID(ctx, source.ID)
  170. wantErr := ErrLoginSourceInUse{args: errutil.Args{"id": source.ID}}
  171. assert.Equal(t, wantErr, err)
  172. })
  173. mock := NewMockLoginSourceFilesStore()
  174. mock.GetByIDFunc.SetDefaultHook(func(id int64) (*LoginSource, error) {
  175. return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
  176. })
  177. setMockLoginSourceFilesStore(t, db, mock)
  178. // Create a login source with name "GitHub2"
  179. source, err := db.Create(ctx,
  180. CreateLoginSourceOpts{
  181. Type: auth.GitHub,
  182. Name: "GitHub2",
  183. Activated: true,
  184. Default: false,
  185. Config: &github.Config{
  186. APIEndpoint: "https://api.github.com",
  187. },
  188. },
  189. )
  190. require.NoError(t, err)
  191. // Delete a non-existent ID is noop
  192. err = db.DeleteByID(ctx, 9999)
  193. require.NoError(t, err)
  194. // We should be able to get it back
  195. _, err = db.GetByID(ctx, source.ID)
  196. require.NoError(t, err)
  197. // Now delete this login source with ID
  198. err = db.DeleteByID(ctx, source.ID)
  199. require.NoError(t, err)
  200. // We should get token not found error
  201. _, err = db.GetByID(ctx, source.ID)
  202. wantErr := ErrLoginSourceNotExist{args: errutil.Args{"id": source.ID}}
  203. assert.Equal(t, wantErr, err)
  204. }
  205. func loginSourcesGetByID(t *testing.T, db *loginSources) {
  206. ctx := context.Background()
  207. mock := NewMockLoginSourceFilesStore()
  208. mock.GetByIDFunc.SetDefaultHook(func(id int64) (*LoginSource, error) {
  209. if id != 101 {
  210. return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
  211. }
  212. return &LoginSource{ID: id}, nil
  213. })
  214. setMockLoginSourceFilesStore(t, db, mock)
  215. expConfig := &github.Config{
  216. APIEndpoint: "https://api.github.com",
  217. }
  218. // Create a login source with name "GitHub"
  219. source, err := db.Create(ctx,
  220. CreateLoginSourceOpts{
  221. Type: auth.GitHub,
  222. Name: "GitHub",
  223. Activated: true,
  224. Default: false,
  225. Config: expConfig,
  226. },
  227. )
  228. require.NoError(t, err)
  229. // Get the one in the database and test the read/write hooks
  230. source, err = db.GetByID(ctx, source.ID)
  231. require.NoError(t, err)
  232. assert.Equal(t, expConfig, source.Provider.Config())
  233. // Get the one in source file store
  234. _, err = db.GetByID(ctx, 101)
  235. require.NoError(t, err)
  236. }
  237. func loginSourcesList(t *testing.T, db *loginSources) {
  238. ctx := context.Background()
  239. mock := NewMockLoginSourceFilesStore()
  240. mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOpts) []*LoginSource {
  241. if opts.OnlyActivated {
  242. return []*LoginSource{
  243. {ID: 1},
  244. }
  245. }
  246. return []*LoginSource{
  247. {ID: 1},
  248. {ID: 2},
  249. }
  250. })
  251. setMockLoginSourceFilesStore(t, db, mock)
  252. // Create two login sources in database, one activated and the other one not
  253. _, err := db.Create(ctx,
  254. CreateLoginSourceOpts{
  255. Type: auth.PAM,
  256. Name: "PAM",
  257. Config: &pam.Config{
  258. ServiceName: "PAM",
  259. },
  260. },
  261. )
  262. require.NoError(t, err)
  263. _, err = db.Create(ctx,
  264. CreateLoginSourceOpts{
  265. Type: auth.GitHub,
  266. Name: "GitHub",
  267. Activated: true,
  268. Config: &github.Config{
  269. APIEndpoint: "https://api.github.com",
  270. },
  271. },
  272. )
  273. require.NoError(t, err)
  274. // List all login sources
  275. sources, err := db.List(ctx, ListLoginSourceOpts{})
  276. require.NoError(t, err)
  277. assert.Equal(t, 4, len(sources), "number of sources")
  278. // Only list activated login sources
  279. sources, err = db.List(ctx, ListLoginSourceOpts{OnlyActivated: true})
  280. require.NoError(t, err)
  281. assert.Equal(t, 2, len(sources), "number of sources")
  282. }
  283. func loginSourcesResetNonDefault(t *testing.T, db *loginSources) {
  284. ctx := context.Background()
  285. mock := NewMockLoginSourceFilesStore()
  286. mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOpts) []*LoginSource {
  287. mockFile := NewMockLoginSourceFileStore()
  288. mockFile.SetGeneralFunc.SetDefaultHook(func(name, value string) {
  289. assert.Equal(t, "is_default", name)
  290. assert.Equal(t, "false", value)
  291. })
  292. return []*LoginSource{
  293. {
  294. File: mockFile,
  295. },
  296. }
  297. })
  298. setMockLoginSourceFilesStore(t, db, mock)
  299. // Create two login sources both have default on
  300. source1, err := db.Create(ctx,
  301. CreateLoginSourceOpts{
  302. Type: auth.PAM,
  303. Name: "PAM",
  304. Default: true,
  305. Config: &pam.Config{
  306. ServiceName: "PAM",
  307. },
  308. },
  309. )
  310. require.NoError(t, err)
  311. source2, err := db.Create(ctx,
  312. CreateLoginSourceOpts{
  313. Type: auth.GitHub,
  314. Name: "GitHub",
  315. Activated: true,
  316. Default: true,
  317. Config: &github.Config{
  318. APIEndpoint: "https://api.github.com",
  319. },
  320. },
  321. )
  322. require.NoError(t, err)
  323. // Set source 1 as default
  324. err = db.ResetNonDefault(ctx, source1)
  325. require.NoError(t, err)
  326. // Verify the default state
  327. source1, err = db.GetByID(ctx, source1.ID)
  328. require.NoError(t, err)
  329. assert.True(t, source1.IsDefault)
  330. source2, err = db.GetByID(ctx, source2.ID)
  331. require.NoError(t, err)
  332. assert.False(t, source2.IsDefault)
  333. }
  334. func loginSourcesSave(t *testing.T, db *loginSources) {
  335. ctx := context.Background()
  336. t.Run("save to database", func(t *testing.T) {
  337. // Create a login source with name "GitHub"
  338. source, err := db.Create(ctx,
  339. CreateLoginSourceOpts{
  340. Type: auth.GitHub,
  341. Name: "GitHub",
  342. Activated: true,
  343. Default: false,
  344. Config: &github.Config{
  345. APIEndpoint: "https://api.github.com",
  346. },
  347. },
  348. )
  349. require.NoError(t, err)
  350. source.IsActived = false
  351. source.Provider = github.NewProvider(&github.Config{
  352. APIEndpoint: "https://api2.github.com",
  353. })
  354. err = db.Save(ctx, source)
  355. require.NoError(t, err)
  356. source, err = db.GetByID(ctx, source.ID)
  357. require.NoError(t, err)
  358. assert.False(t, source.IsActived)
  359. assert.Equal(t, "https://api2.github.com", source.GitHub().APIEndpoint)
  360. })
  361. t.Run("save to file", func(t *testing.T) {
  362. mockFile := NewMockLoginSourceFileStore()
  363. source := &LoginSource{
  364. Provider: github.NewProvider(&github.Config{
  365. APIEndpoint: "https://api.github.com",
  366. }),
  367. File: mockFile,
  368. }
  369. err := db.Save(ctx, source)
  370. require.NoError(t, err)
  371. mockrequire.Called(t, mockFile.SaveFunc)
  372. })
  373. }