More manager tests
This commit is contained in:
parent
57814cf855
commit
bd2ec7b2af
5 changed files with 188 additions and 109 deletions
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue