auth_sqlite_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package auth_test
  2. import (
  3. "github.com/stretchr/testify/require"
  4. "heckel.io/ntfy/auth"
  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 TestSQLiteAuth_FullScenario_Default_DenyAll(t *testing.T) {
  12. a := newTestAuth(t, false, false)
  13. require.Nil(t, a.AddUser("phil", "phil", auth.RoleAdmin))
  14. require.Nil(t, a.AddUser("ben", "ben", auth.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(auth.Everyone, "announcements", true, false))
  20. require.Nil(t, a.AllowAccess(auth.Everyone, "everyonewrite", true, true))
  21. require.Nil(t, a.AllowAccess(auth.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, auth.RoleAdmin, phil.Role)
  27. require.Equal(t, []auth.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, auth.RoleUser, ben.Role)
  33. require.Equal(t, []auth.Grant{
  34. {"mytopic", true, true},
  35. {"readme", true, false},
  36. {"writeme", false, true},
  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, auth.ErrUnauthenticated, err)
  42. // Admin can do everything
  43. require.Nil(t, a.Authorize(phil, "sometopic", auth.PermissionWrite))
  44. require.Nil(t, a.Authorize(phil, "mytopic", auth.PermissionRead))
  45. require.Nil(t, a.Authorize(phil, "readme", auth.PermissionWrite))
  46. require.Nil(t, a.Authorize(phil, "writeme", auth.PermissionWrite))
  47. require.Nil(t, a.Authorize(phil, "announcements", auth.PermissionWrite))
  48. require.Nil(t, a.Authorize(phil, "everyonewrite", auth.PermissionWrite))
  49. // User cannot do everything
  50. require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionWrite))
  51. require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionRead))
  52. require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead))
  53. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "readme", auth.PermissionWrite))
  54. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "writeme", auth.PermissionRead))
  55. require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
  56. require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
  57. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "everyonewrite", auth.PermissionRead))
  58. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "everyonewrite", auth.PermissionWrite))
  59. require.Nil(t, a.Authorize(ben, "announcements", auth.PermissionRead))
  60. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "announcements", auth.PermissionWrite))
  61. // Everyone else can do barely anything
  62. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", auth.PermissionRead))
  63. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", auth.PermissionWrite))
  64. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "mytopic", auth.PermissionRead))
  65. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "mytopic", auth.PermissionWrite))
  66. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "readme", auth.PermissionRead))
  67. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "readme", auth.PermissionWrite))
  68. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "writeme", auth.PermissionRead))
  69. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "writeme", auth.PermissionWrite))
  70. require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "announcements", auth.PermissionWrite))
  71. require.Nil(t, a.Authorize(nil, "announcements", auth.PermissionRead))
  72. require.Nil(t, a.Authorize(nil, "everyonewrite", auth.PermissionRead))
  73. require.Nil(t, a.Authorize(nil, "everyonewrite", auth.PermissionWrite))
  74. require.Nil(t, a.Authorize(nil, "up1234", auth.PermissionWrite)) // Wildcard permission
  75. require.Nil(t, a.Authorize(nil, "up5678", auth.PermissionWrite))
  76. }
  77. func TestSQLiteAuth_AddUser_Invalid(t *testing.T) {
  78. a := newTestAuth(t, false, false)
  79. require.Equal(t, auth.ErrInvalidArgument, a.AddUser(" invalid ", "pass", auth.RoleAdmin))
  80. require.Equal(t, auth.ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
  81. }
  82. func TestSQLiteAuth_AddUser_Timing(t *testing.T) {
  83. a := newTestAuth(t, false, false)
  84. start := time.Now().UnixMilli()
  85. require.Nil(t, a.AddUser("user", "pass", auth.RoleAdmin))
  86. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  87. }
  88. func TestSQLiteAuth_Authenticate_Timing(t *testing.T) {
  89. a := newTestAuth(t, false, false)
  90. require.Nil(t, a.AddUser("user", "pass", auth.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, auth.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, auth.ErrUnauthenticated, err)
  105. require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
  106. }
  107. func TestSQLiteAuth_UserManagement(t *testing.T) {
  108. a := newTestAuth(t, false, false)
  109. require.Nil(t, a.AddUser("phil", "phil", auth.RoleAdmin))
  110. require.Nil(t, a.AddUser("ben", "ben", auth.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(auth.Everyone, "announcements", true, false))
  116. require.Nil(t, a.AllowAccess(auth.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, auth.RoleAdmin, phil.Role)
  123. require.Equal(t, []auth.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, auth.RoleUser, ben.Role)
  129. require.Equal(t, []auth.Grant{
  130. {"mytopic", true, true},
  131. {"readme", true, false},
  132. {"writeme", false, true},
  133. {"everyonewrite", false, false},
  134. }, ben.Grants)
  135. everyone, err := a.User(auth.Everyone)
  136. require.Nil(t, err)
  137. require.Equal(t, "*", everyone.Name)
  138. require.Equal(t, "", everyone.Hash)
  139. require.Equal(t, auth.RoleAnonymous, everyone.Role)
  140. require.Equal(t, []auth.Grant{
  141. {"announcements", true, false},
  142. {"everyonewrite", true, true},
  143. }, everyone.Grants)
  144. // Ben: Before revoking
  145. require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
  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", auth.PermissionRead))
  149. require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionWrite))
  150. require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead))
  151. require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
  152. // Revoke access for "ben" to "mytopic", then check again
  153. require.Nil(t, a.ResetAccess("ben", "mytopic"))
  154. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "mytopic", auth.PermissionWrite)) // Revoked
  155. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "mytopic", auth.PermissionRead)) // Revoked
  156. require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead)) // Unchanged
  157. require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite)) // Unchanged
  158. // Revoke rest of the access
  159. require.Nil(t, a.ResetAccess("ben", ""))
  160. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "readme", auth.PermissionRead)) // Revoked
  161. require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "wrtiteme", auth.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, auth.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 TestSQLiteAuth_ChangePassword(t *testing.T) {
  180. a := newTestAuth(t, false, false)
  181. require.Nil(t, a.AddUser("phil", "phil", auth.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, auth.ErrUnauthenticated, err)
  187. _, err = a.Authenticate("phil", "newpass")
  188. require.Nil(t, err)
  189. }
  190. func TestSQLiteAuth_ChangeRole(t *testing.T) {
  191. a := newTestAuth(t, false, false)
  192. require.Nil(t, a.AddUser("ben", "ben", auth.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, auth.RoleUser, ben.Role)
  198. require.Equal(t, 2, len(ben.Grants))
  199. require.Nil(t, a.ChangeRole("ben", auth.RoleAdmin))
  200. ben, err = a.User("ben")
  201. require.Nil(t, err)
  202. require.Equal(t, auth.RoleAdmin, ben.Role)
  203. require.Equal(t, 0, len(ben.Grants))
  204. }
  205. func newTestAuth(t *testing.T, defaultRead, defaultWrite bool) *auth.SQLiteAuth {
  206. filename := filepath.Join(t.TempDir(), "user.db")
  207. a, err := auth.NewSQLiteAuth(filename, defaultRead, defaultWrite)
  208. require.Nil(t, err)
  209. return a
  210. }