manager_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. package user
  2. import (
  3. "database/sql"
  4. "github.com/stretchr/testify/require"
  5. "path/filepath"
  6. "strings"
  7. "testing"
  8. "time"
  9. )
  10. const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this should also run on a Raspberry Pi without massive resources
  11. func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
  12. a := newTestManager(t, false, false)
  13. require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
  14. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  15. require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
  16. require.Nil(t, a.AllowAccess("ben", "readme", true, false))
  17. require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
  18. require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair!
  19. require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false))
  20. require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true))
  21. require.Nil(t, a.AllowAccess(Everyone, "up*", false, true)) // Everyone can write to /up*
  22. phil, err := a.Authenticate("phil", "phil")
  23. require.Nil(t, err)
  24. require.Equal(t, "phil", phil.Name)
  25. require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
  26. require.Equal(t, RoleAdmin, phil.Role)
  27. require.Equal(t, []Grant{}, phil.Grants)
  28. ben, err := a.Authenticate("ben", "ben")
  29. require.Nil(t, err)
  30. require.Equal(t, "ben", ben.Name)
  31. require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
  32. require.Equal(t, RoleUser, ben.Role)
  33. require.Equal(t, []Grant{
  34. {"mytopic", true, true},
  35. {"writeme", false, true},
  36. {"readme", true, false},
  37. {"everyonewrite", false, false},
  38. }, ben.Grants)
  39. notben, err := a.Authenticate("ben", "this is wrong")
  40. require.Nil(t, notben)
  41. require.Equal(t, ErrUnauthenticated, err)
  42. // Admin can do everything
  43. require.Nil(t, a.Authorize(phil, "sometopic", PermissionWrite))
  44. require.Nil(t, a.Authorize(phil, "mytopic", PermissionRead))
  45. require.Nil(t, a.Authorize(phil, "readme", PermissionWrite))
  46. require.Nil(t, a.Authorize(phil, "writeme", PermissionWrite))
  47. require.Nil(t, a.Authorize(phil, "announcements", PermissionWrite))
  48. require.Nil(t, a.Authorize(phil, "everyonewrite", PermissionWrite))
  49. // User cannot do everything
  50. require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
  51. require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
  52. require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
  53. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionWrite))
  54. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "writeme", PermissionRead))
  55. require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
  56. require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
  57. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionRead))
  58. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionWrite))
  59. require.Nil(t, a.Authorize(ben, "announcements", PermissionRead))
  60. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite))
  61. // Everyone else can do barely anything
  62. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead))
  63. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite))
  64. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionRead))
  65. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionWrite))
  66. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionRead))
  67. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionWrite))
  68. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionRead))
  69. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionWrite))
  70. require.Equal(t, ErrUnauthorized, a.Authorize(nil, "announcements", PermissionWrite))
  71. require.Nil(t, a.Authorize(nil, "announcements", PermissionRead))
  72. require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionRead))
  73. require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionWrite))
  74. require.Nil(t, a.Authorize(nil, "up1234", PermissionWrite)) // Wildcard permission
  75. require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite))
  76. }
  77. func TestManager_AddUser_Invalid(t *testing.T) {
  78. a := newTestManager(t, false, false)
  79. require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
  80. require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
  81. }
  82. func TestManager_AddUser_Timing(t *testing.T) {
  83. a := newTestManager(t, false, false)
  84. start := time.Now().UnixMilli()
  85. require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
  86. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  87. }
  88. func TestManager_Authenticate_Timing(t *testing.T) {
  89. a := newTestManager(t, false, false)
  90. require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
  91. // Timing a correct attempt
  92. start := time.Now().UnixMilli()
  93. _, err := a.Authenticate("user", "pass")
  94. require.Nil(t, err)
  95. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  96. // Timing an incorrect attempt
  97. start = time.Now().UnixMilli()
  98. _, err = a.Authenticate("user", "INCORRECT")
  99. require.Equal(t, ErrUnauthenticated, err)
  100. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  101. // Timing a non-existing user attempt
  102. start = time.Now().UnixMilli()
  103. _, err = a.Authenticate("DOES-NOT-EXIST", "hithere")
  104. require.Equal(t, ErrUnauthenticated, err)
  105. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  106. }
  107. func TestManager_UserManagement(t *testing.T) {
  108. a := newTestManager(t, false, false)
  109. require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
  110. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  111. require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
  112. require.Nil(t, a.AllowAccess("ben", "readme", true, false))
  113. require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
  114. require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair!
  115. require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false))
  116. require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true))
  117. // Query user details
  118. phil, err := a.User("phil")
  119. require.Nil(t, err)
  120. require.Equal(t, "phil", phil.Name)
  121. require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
  122. require.Equal(t, RoleAdmin, phil.Role)
  123. require.Equal(t, []Grant{}, phil.Grants)
  124. ben, err := a.User("ben")
  125. require.Nil(t, err)
  126. require.Equal(t, "ben", ben.Name)
  127. require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
  128. require.Equal(t, RoleUser, ben.Role)
  129. require.Equal(t, []Grant{
  130. {"mytopic", true, true},
  131. {"writeme", false, true},
  132. {"readme", true, false},
  133. {"everyonewrite", false, false},
  134. }, ben.Grants)
  135. everyone, err := a.User(Everyone)
  136. require.Nil(t, err)
  137. require.Equal(t, "*", everyone.Name)
  138. require.Equal(t, "", everyone.Hash)
  139. require.Equal(t, RoleAnonymous, everyone.Role)
  140. require.Equal(t, []Grant{
  141. {"everyonewrite", true, true},
  142. {"announcements", true, false},
  143. }, everyone.Grants)
  144. // Ben: Before revoking
  145. require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) // Overwrite!
  146. require.Nil(t, a.AllowAccess("ben", "readme", true, false))
  147. require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
  148. require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
  149. require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
  150. require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
  151. require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
  152. // Revoke access for "ben" to "mytopic", then check again
  153. require.Nil(t, a.ResetAccess("ben", "mytopic"))
  154. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionWrite)) // Revoked
  155. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionRead)) // Revoked
  156. require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) // Unchanged
  157. require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) // Unchanged
  158. // Revoke rest of the access
  159. require.Nil(t, a.ResetAccess("ben", ""))
  160. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionRead)) // Revoked
  161. require.Equal(t, ErrUnauthorized, a.Authorize(ben, "wrtiteme", PermissionWrite)) // Revoked
  162. // User list
  163. users, err := a.Users()
  164. require.Nil(t, err)
  165. require.Equal(t, 3, len(users))
  166. require.Equal(t, "phil", users[0].Name)
  167. require.Equal(t, "ben", users[1].Name)
  168. require.Equal(t, "*", users[2].Name)
  169. // Remove user
  170. require.Nil(t, a.RemoveUser("ben"))
  171. _, err = a.User("ben")
  172. require.Equal(t, ErrNotFound, err)
  173. users, err = a.Users()
  174. require.Nil(t, err)
  175. require.Equal(t, 2, len(users))
  176. require.Equal(t, "phil", users[0].Name)
  177. require.Equal(t, "*", users[1].Name)
  178. }
  179. func TestManager_ChangePassword(t *testing.T) {
  180. a := newTestManager(t, false, false)
  181. require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
  182. _, err := a.Authenticate("phil", "phil")
  183. require.Nil(t, err)
  184. require.Nil(t, a.ChangePassword("phil", "newpass"))
  185. _, err = a.Authenticate("phil", "phil")
  186. require.Equal(t, ErrUnauthenticated, err)
  187. _, err = a.Authenticate("phil", "newpass")
  188. require.Nil(t, err)
  189. }
  190. func TestManager_ChangeRole(t *testing.T) {
  191. a := newTestManager(t, false, false)
  192. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  193. require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
  194. require.Nil(t, a.AllowAccess("ben", "readme", true, false))
  195. ben, err := a.User("ben")
  196. require.Nil(t, err)
  197. require.Equal(t, RoleUser, ben.Role)
  198. require.Equal(t, 2, len(ben.Grants))
  199. require.Nil(t, a.ChangeRole("ben", RoleAdmin))
  200. ben, err = a.User("ben")
  201. require.Nil(t, err)
  202. require.Equal(t, RoleAdmin, ben.Role)
  203. require.Equal(t, 0, len(ben.Grants))
  204. }
  205. func TestManager_Token_Valid(t *testing.T) {
  206. a := newTestManager(t, false, false)
  207. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  208. u, err := a.User("ben")
  209. require.Nil(t, err)
  210. // Create token for user
  211. token, err := a.CreateToken(u)
  212. require.Nil(t, err)
  213. require.NotEmpty(t, token.Value)
  214. require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires.Unix())
  215. u2, err := a.AuthenticateToken(token.Value)
  216. require.Nil(t, err)
  217. require.Equal(t, u.Name, u2.Name)
  218. require.Equal(t, token.Value, u2.Token)
  219. // Remove token and auth again
  220. require.Nil(t, a.RemoveToken(u2))
  221. u3, err := a.AuthenticateToken(token.Value)
  222. require.Equal(t, ErrUnauthenticated, err)
  223. require.Nil(t, u3)
  224. }
  225. func TestManager_Token_Invalid(t *testing.T) {
  226. a := newTestManager(t, false, false)
  227. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  228. u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
  229. require.Nil(t, u)
  230. require.Equal(t, ErrUnauthenticated, err)
  231. u, err = a.AuthenticateToken("not long enough anyway")
  232. require.Nil(t, u)
  233. require.Equal(t, ErrUnauthenticated, err)
  234. }
  235. func TestManager_Token_Expire(t *testing.T) {
  236. a := newTestManager(t, false, false)
  237. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  238. u, err := a.User("ben")
  239. require.Nil(t, err)
  240. // Create tokens for user
  241. token1, err := a.CreateToken(u)
  242. require.Nil(t, err)
  243. require.NotEmpty(t, token1.Value)
  244. require.True(t, time.Now().Add(71*time.Hour).Unix() < token1.Expires.Unix())
  245. token2, err := a.CreateToken(u)
  246. require.Nil(t, err)
  247. require.NotEmpty(t, token2.Value)
  248. require.NotEqual(t, token1.Value, token2.Value)
  249. require.True(t, time.Now().Add(71*time.Hour).Unix() < token2.Expires.Unix())
  250. // See that tokens work
  251. _, err = a.AuthenticateToken(token1.Value)
  252. require.Nil(t, err)
  253. _, err = a.AuthenticateToken(token2.Value)
  254. require.Nil(t, err)
  255. // Modify token expiration in database
  256. _, err = a.db.Exec("UPDATE user_token SET expires = 1 WHERE token = ?", token1.Value)
  257. require.Nil(t, err)
  258. // Now token1 shouldn't work anymore
  259. _, err = a.AuthenticateToken(token1.Value)
  260. require.Equal(t, ErrUnauthenticated, err)
  261. result, err := a.db.Query("SELECT * from user_token WHERE token = ?", token1.Value)
  262. require.Nil(t, err)
  263. require.True(t, result.Next()) // Still a matching row
  264. require.Nil(t, result.Close())
  265. // Expire tokens and check database rows
  266. require.Nil(t, a.RemoveExpiredTokens())
  267. result, err = a.db.Query("SELECT * from user_token WHERE token = ?", token1.Value)
  268. require.Nil(t, err)
  269. require.False(t, result.Next()) // No matching row!
  270. require.Nil(t, result.Close())
  271. }
  272. func TestManager_EnqueueStats(t *testing.T) {
  273. a, err := newManager(filepath.Join(t.TempDir(), "db"), true, true, time.Hour, 1500*time.Millisecond)
  274. require.Nil(t, err)
  275. require.Nil(t, a.AddUser("ben", "ben", RoleUser))
  276. // Baseline: No messages or emails
  277. u, err := a.User("ben")
  278. require.Nil(t, err)
  279. require.Equal(t, int64(0), u.Stats.Messages)
  280. require.Equal(t, int64(0), u.Stats.Emails)
  281. u.Stats.Messages = 11
  282. u.Stats.Emails = 2
  283. a.EnqueueStats(u)
  284. // Still no change, because it's queued asynchronously
  285. u, err = a.User("ben")
  286. require.Nil(t, err)
  287. require.Equal(t, int64(0), u.Stats.Messages)
  288. require.Equal(t, int64(0), u.Stats.Emails)
  289. // After 2 seconds they should be persisted
  290. time.Sleep(2 * time.Second)
  291. u, err = a.User("ben")
  292. require.Nil(t, err)
  293. require.Equal(t, int64(11), u.Stats.Messages)
  294. require.Equal(t, int64(2), u.Stats.Emails)
  295. }
  296. func TestSqliteCache_Migration_From1(t *testing.T) {
  297. filename := filepath.Join(t.TempDir(), "user.db")
  298. db, err := sql.Open("sqlite3", filename)
  299. require.Nil(t, err)
  300. // Create "version 1" schema
  301. _, err = db.Exec(`
  302. BEGIN;
  303. CREATE TABLE IF NOT EXISTS user (
  304. user TEXT NOT NULL PRIMARY KEY,
  305. pass TEXT NOT NULL,
  306. role TEXT NOT NULL
  307. );
  308. CREATE TABLE IF NOT EXISTS access (
  309. user TEXT NOT NULL,
  310. topic TEXT NOT NULL,
  311. read INT NOT NULL,
  312. write INT NOT NULL,
  313. PRIMARY KEY (topic, user)
  314. );
  315. CREATE TABLE IF NOT EXISTS schemaVersion (
  316. id INT PRIMARY KEY,
  317. version INT NOT NULL
  318. );
  319. INSERT INTO schemaVersion (id, version) VALUES (1, 1);
  320. COMMIT;
  321. `)
  322. require.Nil(t, err)
  323. // Insert a bunch of users and ACL entries
  324. _, err = db.Exec(`
  325. BEGIN;
  326. INSERT INTO user (user, pass, role) VALUES ('ben', '$2a$10$EEp6gBheOsqEFsXlo523E.gBVoeg1ytphXiEvTPlNzkenBlHZBPQy', 'user');
  327. INSERT INTO user (user, pass, role) VALUES ('phil', '$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C', 'admin');
  328. INSERT INTO access (user, topic, read, write) VALUES ('ben', 'stats', 1, 1);
  329. INSERT INTO access (user, topic, read, write) VALUES ('ben', 'secret', 1, 0);
  330. INSERT INTO access (user, topic, read, write) VALUES ('*', 'stats', 1, 0);
  331. COMMIT;
  332. `)
  333. require.Nil(t, err)
  334. // Create manager to trigger migration
  335. a := newTestManagerFromFile(t, filename, false, false, userTokenExpiryDuration, userStatsQueueWriterInterval)
  336. checkSchemaVersion(t, a.db)
  337. users, err := a.Users()
  338. require.Nil(t, err)
  339. require.Equal(t, 3, len(users))
  340. phil, ben, everyone := users[0], users[1], users[2]
  341. require.Equal(t, "phil", phil.Name)
  342. require.Equal(t, RoleAdmin, phil.Role)
  343. require.Equal(t, 0, len(phil.Grants))
  344. require.Equal(t, "ben", ben.Name)
  345. require.Equal(t, RoleUser, ben.Role)
  346. require.Equal(t, 2, len(ben.Grants))
  347. require.Equal(t, "stats", ben.Grants[0].TopicPattern)
  348. require.Equal(t, true, ben.Grants[0].AllowRead)
  349. require.Equal(t, true, ben.Grants[0].AllowWrite)
  350. require.Equal(t, "secret", ben.Grants[1].TopicPattern)
  351. require.Equal(t, true, ben.Grants[1].AllowRead)
  352. require.Equal(t, false, ben.Grants[1].AllowWrite)
  353. require.Equal(t, Everyone, everyone.Name)
  354. require.Equal(t, RoleAnonymous, everyone.Role)
  355. require.Equal(t, 1, len(everyone.Grants))
  356. require.Equal(t, "stats", everyone.Grants[0].TopicPattern)
  357. require.Equal(t, true, everyone.Grants[0].AllowRead)
  358. require.Equal(t, false, everyone.Grants[0].AllowWrite)
  359. }
  360. func checkSchemaVersion(t *testing.T, db *sql.DB) {
  361. rows, err := db.Query(`SELECT version FROM schemaVersion`)
  362. require.Nil(t, err)
  363. require.True(t, rows.Next())
  364. var schemaVersion int
  365. require.Nil(t, rows.Scan(&schemaVersion))
  366. require.Equal(t, currentSchemaVersion, schemaVersion)
  367. require.Nil(t, rows.Close())
  368. }
  369. func newTestManager(t *testing.T, defaultRead, defaultWrite bool) *Manager {
  370. return newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), defaultRead, defaultWrite, userTokenExpiryDuration, userStatsQueueWriterInterval)
  371. }
  372. func newTestManagerFromFile(t *testing.T, filename string, defaultRead, defaultWrite bool, tokenExpiryDuration, statsWriterInterval time.Duration) *Manager {
  373. a, err := newManager(filename, defaultRead, defaultWrite, tokenExpiryDuration, statsWriterInterval)
  374. require.Nil(t, err)
  375. return a
  376. }