More manager tests

This commit is contained in:
binwiederhier 2022-12-29 11:09:45 -05:00
parent 57814cf855
commit bd2ec7b2af
5 changed files with 188 additions and 109 deletions

View file

@ -53,13 +53,7 @@ import (
rate limiting: rate limiting:
- login/account endpoints - login/account endpoints
Tests: Tests:
- APIs
- CRUD tokens
- Expire tokens
- userManager can be nil
- visitor with/without user - visitor with/without user
- userManager.<NEWSTUFF>
Later: Later:
- Password reset - Password reset
- Pricing - Pricing
@ -569,7 +563,7 @@ func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*mes
} }
} }
v.IncrMessages() v.IncrMessages()
if v.user != nil { if s.userManager != nil && v.user != nil {
s.userManager.EnqueueStats(v.user) s.userManager.EnqueueStats(v.user)
} }
s.mu.Lock() s.mu.Lock()
@ -1342,7 +1336,7 @@ func (s *Server) sendDelayedMessages() error {
} }
for _, m := range messages { for _, m := range messages {
var v *visitor var v *visitor
if m.User != "" { if s.userManager != nil && m.User != "" {
user, err := s.userManager.User(m.User) user, err := s.userManager.User(m.User)
if err != nil { if err != nil {
log.Warn("%s Error sending delayed message: %s", logMessagePrefix(v, m), err.Error()) log.Warn("%s Error sending delayed message: %s", logMessagePrefix(v, m), err.Error())

View file

@ -8,7 +8,8 @@ import (
) )
const ( const (
jsonBodyBytesLimit = 4096 jsonBodyBytesLimit = 4096
subscriptionIDLength = 16
) )
func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
@ -232,7 +233,7 @@ func (s *Server) handleAccountSubscriptionAdd(w http.ResponseWriter, r *http.Req
} }
} }
if newSubscription.ID == "" { if newSubscription.ID == "" {
newSubscription.ID = util.RandomString(16) newSubscription.ID = util.RandomString(subscriptionIDLength)
v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, newSubscription) v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, newSubscription)
if err := s.userManager.ChangeSettings(v.user); err != nil { if err := s.userManager.ChangeSettings(v.user); err != nil {
return err return err

View file

@ -80,7 +80,7 @@ const (
FROM user u FROM user u
JOIN user_token t on u.id = t.user_id JOIN user_token t on u.id = t.user_id
LEFT JOIN plan p on p.id = u.plan_id LEFT JOIN plan p on p.id = u.plan_id
WHERE t.token = ? WHERE t.token = ? AND t.expires >= ?
` `
selectTopicPermsQuery = ` selectTopicPermsQuery = `
SELECT read, write SELECT read, write
@ -138,17 +138,23 @@ const (
// Manager is an implementation of Manager. It stores users and access control list // Manager is an implementation of Manager. It stores users and access control list
// in a SQLite database. // in a SQLite database.
type Manager struct { type Manager struct {
db *sql.DB db *sql.DB
defaultRead bool defaultRead bool // Default read permission if no ACL matches
defaultWrite bool defaultWrite bool // Default write permission if no ACL matches
statsQueue map[string]*User // Username -> User, for "unimportant" user updates statsQueue map[string]*User // Username -> User, for "unimportant" user updates
mu sync.Mutex tokenExpiryInterval time.Duration // Duration after which tokens expire, and by which tokens are extended
mu sync.Mutex
} }
var _ Auther = (*Manager)(nil) var _ Auther = (*Manager)(nil)
// NewManager creates a new Manager instance // NewManager creates a new Manager instance
func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, error) { func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, error) {
return newManager(filename, defaultRead, defaultWrite, userTokenExpiryDuration, userStatsQueueWriterInterval)
}
// NewManager creates a new Manager instance
func newManager(filename string, defaultRead, defaultWrite bool, tokenExpiryDuration, statsWriterInterval time.Duration) (*Manager, error) {
db, err := sql.Open("sqlite3", filename) db, err := sql.Open("sqlite3", filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -157,12 +163,13 @@ func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, erro
return nil, err return nil, err
} }
manager := &Manager{ manager := &Manager{
db: db, db: db,
defaultRead: defaultRead, defaultRead: defaultRead,
defaultWrite: defaultWrite, defaultWrite: defaultWrite,
statsQueue: make(map[string]*User), statsQueue: make(map[string]*User),
tokenExpiryInterval: tokenExpiryDuration,
} }
go manager.userStatsQueueWriter() go manager.userStatsQueueWriter(statsWriterInterval)
return manager, nil return manager, nil
} }
@ -263,8 +270,8 @@ func (a *Manager) EnqueueStats(user *User) {
a.statsQueue[user.Name] = user a.statsQueue[user.Name] = user
} }
func (a *Manager) userStatsQueueWriter() { func (a *Manager) userStatsQueueWriter(interval time.Duration) {
ticker := time.NewTicker(userStatsQueueWriterInterval) ticker := time.NewTicker(interval)
for range ticker.C { for range ticker.C {
if err := a.writeUserStatsQueue(); err != nil { if err := a.writeUserStatsQueue(); err != nil {
log.Warn("UserManager: Writing user stats queue failed: %s", err.Error()) log.Warn("UserManager: Writing user stats queue failed: %s", err.Error())
@ -409,7 +416,7 @@ func (a *Manager) User(username string) (*User, error) {
} }
func (a *Manager) userByToken(token string) (*User, error) { func (a *Manager) userByToken(token string) (*User, error) {
rows, err := a.db.Query(selectUserByTokenQuery, token) rows, err := a.db.Query(selectUserByTokenQuery, token, time.Now().Unix())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1,8 +1,7 @@
package user_test package user
import ( import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"heckel.io/ntfy/user"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -13,29 +12,29 @@ const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this sh
func TestManager_FullScenario_Default_DenyAll(t *testing.T) { func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "readme", true, false))
require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair! require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair!
require.Nil(t, a.AllowAccess(user.Everyone, "announcements", true, false)) require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false))
require.Nil(t, a.AllowAccess(user.Everyone, "everyonewrite", true, true)) require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true))
require.Nil(t, a.AllowAccess(user.Everyone, "up*", false, true)) // Everyone can write to /up* require.Nil(t, a.AllowAccess(Everyone, "up*", false, true)) // Everyone can write to /up*
phil, err := a.Authenticate("phil", "phil") phil, err := a.Authenticate("phil", "phil")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "phil", phil.Name) require.Equal(t, "phil", phil.Name)
require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$")) require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
require.Equal(t, user.RoleAdmin, phil.Role) require.Equal(t, RoleAdmin, phil.Role)
require.Equal(t, []user.Grant{}, phil.Grants) require.Equal(t, []Grant{}, phil.Grants)
ben, err := a.Authenticate("ben", "ben") ben, err := a.Authenticate("ben", "ben")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "ben", ben.Name) require.Equal(t, "ben", ben.Name)
require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$")) require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
require.Equal(t, user.RoleUser, ben.Role) require.Equal(t, RoleUser, ben.Role)
require.Equal(t, []user.Grant{ require.Equal(t, []Grant{
{"mytopic", true, true}, {"mytopic", true, true},
{"writeme", false, true}, {"writeme", false, true},
{"readme", true, false}, {"readme", true, false},
@ -44,62 +43,62 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
notben, err := a.Authenticate("ben", "this is wrong") notben, err := a.Authenticate("ben", "this is wrong")
require.Nil(t, notben) require.Nil(t, notben)
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
// Admin can do everything // Admin can do everything
require.Nil(t, a.Authorize(phil, "sometopic", user.PermissionWrite)) require.Nil(t, a.Authorize(phil, "sometopic", PermissionWrite))
require.Nil(t, a.Authorize(phil, "mytopic", user.PermissionRead)) require.Nil(t, a.Authorize(phil, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(phil, "readme", user.PermissionWrite)) require.Nil(t, a.Authorize(phil, "readme", PermissionWrite))
require.Nil(t, a.Authorize(phil, "writeme", user.PermissionWrite)) require.Nil(t, a.Authorize(phil, "writeme", PermissionWrite))
require.Nil(t, a.Authorize(phil, "announcements", user.PermissionWrite)) require.Nil(t, a.Authorize(phil, "announcements", PermissionWrite))
require.Nil(t, a.Authorize(phil, "everyonewrite", user.PermissionWrite)) require.Nil(t, a.Authorize(phil, "everyonewrite", PermissionWrite))
// User cannot do everything // User cannot do everything
require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionWrite)) require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionRead)) require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "readme", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "writeme", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(ben, "writeme", PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "everyonewrite", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "everyonewrite", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionWrite))
require.Nil(t, a.Authorize(ben, "announcements", user.PermissionRead)) require.Nil(t, a.Authorize(ben, "announcements", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "announcements", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite))
// Everyone else can do barely anything // Everyone else can do barely anything
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "mytopic", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "mytopic", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "readme", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "readme", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "writeme", user.PermissionRead)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionRead))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "writeme", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionWrite))
require.Equal(t, user.ErrUnauthorized, a.Authorize(nil, "announcements", user.PermissionWrite)) require.Equal(t, ErrUnauthorized, a.Authorize(nil, "announcements", PermissionWrite))
require.Nil(t, a.Authorize(nil, "announcements", user.PermissionRead)) require.Nil(t, a.Authorize(nil, "announcements", PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", user.PermissionRead)) require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", user.PermissionWrite)) require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionWrite))
require.Nil(t, a.Authorize(nil, "up1234", user.PermissionWrite)) // Wildcard permission require.Nil(t, a.Authorize(nil, "up1234", PermissionWrite)) // Wildcard permission
require.Nil(t, a.Authorize(nil, "up5678", user.PermissionWrite)) require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite))
} }
func TestManager_AddUser_Invalid(t *testing.T) { func TestManager_AddUser_Invalid(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Equal(t, user.ErrInvalidArgument, a.AddUser(" invalid ", "pass", user.RoleAdmin)) require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
require.Equal(t, user.ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role")) require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
} }
func TestManager_AddUser_Timing(t *testing.T) { func TestManager_AddUser_Timing(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
start := time.Now().UnixMilli() start := time.Now().UnixMilli()
require.Nil(t, a.AddUser("user", "pass", user.RoleAdmin)) require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
} }
func TestManager_Authenticate_Timing(t *testing.T) { func TestManager_Authenticate_Timing(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("user", "pass", user.RoleAdmin)) require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
// Timing a correct attempt // Timing a correct attempt
start := time.Now().UnixMilli() start := time.Now().UnixMilli()
@ -110,53 +109,53 @@ func TestManager_Authenticate_Timing(t *testing.T) {
// Timing an incorrect attempt // Timing an incorrect attempt
start = time.Now().UnixMilli() start = time.Now().UnixMilli()
_, err = a.Authenticate("user", "INCORRECT") _, err = a.Authenticate("user", "INCORRECT")
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
// Timing a non-existing user attempt // Timing a non-existing user attempt
start = time.Now().UnixMilli() start = time.Now().UnixMilli()
_, err = a.Authenticate("DOES-NOT-EXIST", "hithere") _, err = a.Authenticate("DOES-NOT-EXIST", "hithere")
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis) require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
} }
func TestManager_UserManagement(t *testing.T) { func TestManager_UserManagement(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "readme", true, false))
require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair! require.Nil(t, a.AllowAccess("ben", "everyonewrite", false, false)) // How unfair!
require.Nil(t, a.AllowAccess(user.Everyone, "announcements", true, false)) require.Nil(t, a.AllowAccess(Everyone, "announcements", true, false))
require.Nil(t, a.AllowAccess(user.Everyone, "everyonewrite", true, true)) require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", true, true))
// Query user details // Query user details
phil, err := a.User("phil") phil, err := a.User("phil")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "phil", phil.Name) require.Equal(t, "phil", phil.Name)
require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$")) require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
require.Equal(t, user.RoleAdmin, phil.Role) require.Equal(t, RoleAdmin, phil.Role)
require.Equal(t, []user.Grant{}, phil.Grants) require.Equal(t, []Grant{}, phil.Grants)
ben, err := a.User("ben") ben, err := a.User("ben")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "ben", ben.Name) require.Equal(t, "ben", ben.Name)
require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$")) require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
require.Equal(t, user.RoleUser, ben.Role) require.Equal(t, RoleUser, ben.Role)
require.Equal(t, []user.Grant{ require.Equal(t, []Grant{
{"mytopic", true, true}, {"mytopic", true, true},
{"writeme", false, true}, {"writeme", false, true},
{"readme", true, false}, {"readme", true, false},
{"everyonewrite", false, false}, {"everyonewrite", false, false},
}, ben.Grants) }, ben.Grants)
everyone, err := a.User(user.Everyone) everyone, err := a.User(Everyone)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, "*", everyone.Name) require.Equal(t, "*", everyone.Name)
require.Equal(t, "", everyone.Hash) require.Equal(t, "", everyone.Hash)
require.Equal(t, user.RoleAnonymous, everyone.Role) require.Equal(t, RoleAnonymous, everyone.Role)
require.Equal(t, []user.Grant{ require.Equal(t, []Grant{
{"everyonewrite", true, true}, {"everyonewrite", true, true},
{"announcements", true, false}, {"announcements", true, false},
}, everyone.Grants) }, everyone.Grants)
@ -165,22 +164,22 @@ func TestManager_UserManagement(t *testing.T) {
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) // Overwrite! require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) // Overwrite!
require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "readme", true, false))
require.Nil(t, a.AllowAccess("ben", "writeme", false, true)) require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionRead)) require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(ben, "mytopic", user.PermissionWrite)) require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
// Revoke access for "ben" to "mytopic", then check again // Revoke access for "ben" to "mytopic", then check again
require.Nil(t, a.ResetAccess("ben", "mytopic")) require.Nil(t, a.ResetAccess("ben", "mytopic"))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "mytopic", user.PermissionWrite)) // Revoked require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionWrite)) // Revoked
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "mytopic", user.PermissionRead)) // Revoked require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionRead)) // Revoked
require.Nil(t, a.Authorize(ben, "readme", user.PermissionRead)) // Unchanged require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) // Unchanged
require.Nil(t, a.Authorize(ben, "writeme", user.PermissionWrite)) // Unchanged require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) // Unchanged
// Revoke rest of the access // Revoke rest of the access
require.Nil(t, a.ResetAccess("ben", "")) require.Nil(t, a.ResetAccess("ben", ""))
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "readme", user.PermissionRead)) // Revoked require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionRead)) // Revoked
require.Equal(t, user.ErrUnauthorized, a.Authorize(ben, "wrtiteme", user.PermissionWrite)) // Revoked require.Equal(t, ErrUnauthorized, a.Authorize(ben, "wrtiteme", PermissionWrite)) // Revoked
// User list // User list
users, err := a.Users() users, err := a.Users()
@ -193,7 +192,7 @@ func TestManager_UserManagement(t *testing.T) {
// Remove user // Remove user
require.Nil(t, a.RemoveUser("ben")) require.Nil(t, a.RemoveUser("ben"))
_, err = a.User("ben") _, err = a.User("ben")
require.Equal(t, user.ErrNotFound, err) require.Equal(t, ErrNotFound, err)
users, err = a.Users() users, err = a.Users()
require.Nil(t, err) require.Nil(t, err)
@ -204,40 +203,40 @@ func TestManager_UserManagement(t *testing.T) {
func TestManager_ChangePassword(t *testing.T) { func TestManager_ChangePassword(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", user.RoleAdmin)) require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
_, err := a.Authenticate("phil", "phil") _, err := a.Authenticate("phil", "phil")
require.Nil(t, err) require.Nil(t, err)
require.Nil(t, a.ChangePassword("phil", "newpass")) require.Nil(t, a.ChangePassword("phil", "newpass"))
_, err = a.Authenticate("phil", "phil") _, err = a.Authenticate("phil", "phil")
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
_, err = a.Authenticate("phil", "newpass") _, err = a.Authenticate("phil", "newpass")
require.Nil(t, err) require.Nil(t, err)
} }
func TestManager_ChangeRole(t *testing.T) { func TestManager_ChangeRole(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true)) require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false)) require.Nil(t, a.AllowAccess("ben", "readme", true, false))
ben, err := a.User("ben") ben, err := a.User("ben")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, user.RoleUser, ben.Role) require.Equal(t, RoleUser, ben.Role)
require.Equal(t, 2, len(ben.Grants)) require.Equal(t, 2, len(ben.Grants))
require.Nil(t, a.ChangeRole("ben", user.RoleAdmin)) require.Nil(t, a.ChangeRole("ben", RoleAdmin))
ben, err = a.User("ben") ben, err = a.User("ben")
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, user.RoleAdmin, ben.Role) require.Equal(t, RoleAdmin, ben.Role)
require.Equal(t, 0, len(ben.Grants)) require.Equal(t, 0, len(ben.Grants))
} }
func TestManager_Token_Valid(t *testing.T) { func TestManager_Token_Valid(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
u, err := a.User("ben") u, err := a.User("ben")
require.Nil(t, err) require.Nil(t, err)
@ -256,26 +255,103 @@ func TestManager_Token_Valid(t *testing.T) {
// Remove token and auth again // Remove token and auth again
require.Nil(t, a.RemoveToken(u2)) require.Nil(t, a.RemoveToken(u2))
u3, err := a.AuthenticateToken(token.Value) u3, err := a.AuthenticateToken(token.Value)
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
require.Nil(t, u3) require.Nil(t, u3)
} }
func TestManager_Token_Invalid(t *testing.T) { func TestManager_Token_Invalid(t *testing.T) {
a := newTestManager(t, false, false) a := newTestManager(t, false, false)
require.Nil(t, a.AddUser("ben", "ben", user.RoleUser)) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
require.Nil(t, u) require.Nil(t, u)
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
u, err = a.AuthenticateToken("not long enough anyway") u, err = a.AuthenticateToken("not long enough anyway")
require.Nil(t, u) require.Nil(t, u)
require.Equal(t, user.ErrUnauthenticated, err) require.Equal(t, ErrUnauthenticated, err)
} }
func newTestManager(t *testing.T, defaultRead, defaultWrite bool) *user.Manager { func TestManager_Token_Expire(t *testing.T) {
filename := filepath.Join(t.TempDir(), "user.db") a := newTestManager(t, false, false)
a, err := user.NewManager(filename, defaultRead, defaultWrite) require.Nil(t, a.AddUser("ben", "ben", RoleUser))
u, err := a.User("ben")
require.Nil(t, err)
// Create tokens for user
token1, err := a.CreateToken(u)
require.Nil(t, err)
require.NotEmpty(t, token1.Value)
require.True(t, time.Now().Add(71*time.Hour).Unix() < token1.Expires.Unix())
token2, err := a.CreateToken(u)
require.Nil(t, err)
require.NotEmpty(t, token2.Value)
require.NotEqual(t, token1.Value, token2.Value)
require.True(t, time.Now().Add(71*time.Hour).Unix() < token2.Expires.Unix())
// See that tokens work
_, err = a.AuthenticateToken(token1.Value)
require.Nil(t, err)
_, err = a.AuthenticateToken(token2.Value)
require.Nil(t, err)
// Modify token expiration in database
_, err = a.db.Exec("UPDATE user_token SET expires = 1 WHERE token = ?", token1.Value)
require.Nil(t, err)
// Now token1 shouldn't work anymore
_, err = a.AuthenticateToken(token1.Value)
require.Equal(t, ErrUnauthenticated, err)
result, err := a.db.Query("SELECT * from user_token WHERE token = ?", token1.Value)
require.Nil(t, err)
require.True(t, result.Next()) // Still a matching row
require.Nil(t, result.Close())
// Expire tokens and check database rows
require.Nil(t, a.RemoveExpiredTokens())
result, err = a.db.Query("SELECT * from user_token WHERE token = ?", token1.Value)
require.Nil(t, err)
require.False(t, result.Next()) // No matching row!
require.Nil(t, result.Close())
}
func TestManager_EnqueueStats(t *testing.T) {
a, err := newManager(filepath.Join(t.TempDir(), "db"), true, true, time.Hour, 1500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
// Baseline: No messages or emails
u, err := a.User("ben")
require.Nil(t, err)
require.Equal(t, int64(0), u.Stats.Messages)
require.Equal(t, int64(0), u.Stats.Emails)
u.Stats.Messages = 11
u.Stats.Emails = 2
a.EnqueueStats(u)
// Still no change, because it's queued asynchronously
u, err = a.User("ben")
require.Nil(t, err)
require.Equal(t, int64(0), u.Stats.Messages)
require.Equal(t, int64(0), u.Stats.Emails)
// After 2 seconds they should be persisted
time.Sleep(2 * time.Second)
u, err = a.User("ben")
require.Nil(t, err)
require.Equal(t, int64(11), u.Stats.Messages)
require.Equal(t, int64(2), u.Stats.Emails)
}
func newTestManager(t *testing.T, defaultRead, defaultWrite bool) *Manager {
a, err := NewManager(filepath.Join(t.TempDir(), "db"), defaultRead, defaultWrite)
require.Nil(t, err) require.Nil(t, err)
return a return a
} }

View file

@ -31,6 +31,7 @@ var (
noQuotesRegex = regexp.MustCompile(`^[-_./:@a-zA-Z0-9]+$`) noQuotesRegex = regexp.MustCompile(`^[-_./:@a-zA-Z0-9]+$`)
) )
// Errors for ReadJSON and ReadJSONWithLimit functions
var ( var (
ErrInvalidJSON = errors.New("invalid JSON") ErrInvalidJSON = errors.New("invalid JSON")
ErrTooLargeJSON = errors.New("too large JSON") ErrTooLargeJSON = errors.New("too large JSON")