1
0

login_sources_test.go 12 KB

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