1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-11-23 11:49:19 +01:00
ntfy/auth/auth_sqlite_test.go
binwiederhier c2f16f740b Stuff
2022-12-14 23:11:22 -05:00

243 lines
10 KiB
Go

package auth_test
import (
"github.com/stretchr/testify/require"
"heckel.io/ntfy/auth"
"path/filepath"
"strings"
"testing"
"time"
)
const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this should also run on a Raspberry Pi without massive resources
func TestSQLiteAuth_FullScenario_Default_DenyAll(t *testing.T) {
a := newTestAuth(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", auth.RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", auth.RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false))
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(auth.Everyone, "announcements", true, false))
require.Nil(t, a.AllowAccess(auth.Everyone, "everyonewrite", true, true))
require.Nil(t, a.AllowAccess(auth.Everyone, "up*", false, true)) // Everyone can write to /up*
phil, err := a.Authenticate("phil", "phil")
require.Nil(t, err)
require.Equal(t, "phil", phil.Name)
require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
require.Equal(t, auth.RoleAdmin, phil.Role)
require.Equal(t, []auth.Grant{}, phil.Grants)
ben, err := a.Authenticate("ben", "ben")
require.Nil(t, err)
require.Equal(t, "ben", ben.Name)
require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
require.Equal(t, auth.RoleUser, ben.Role)
require.Equal(t, []auth.Grant{
{"mytopic", true, true},
{"readme", true, false},
{"writeme", false, true},
{"everyonewrite", false, false},
}, ben.Grants)
notben, err := a.Authenticate("ben", "this is wrong")
require.Nil(t, notben)
require.Equal(t, auth.ErrUnauthenticated, err)
// Admin can do everything
require.Nil(t, a.Authorize(phil, "sometopic", auth.PermissionWrite))
require.Nil(t, a.Authorize(phil, "mytopic", auth.PermissionRead))
require.Nil(t, a.Authorize(phil, "readme", auth.PermissionWrite))
require.Nil(t, a.Authorize(phil, "writeme", auth.PermissionWrite))
require.Nil(t, a.Authorize(phil, "announcements", auth.PermissionWrite))
require.Nil(t, a.Authorize(phil, "everyonewrite", auth.PermissionWrite))
// User cannot do everything
require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionWrite))
require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionRead))
require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "readme", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "writeme", auth.PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "everyonewrite", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "everyonewrite", auth.PermissionWrite))
require.Nil(t, a.Authorize(ben, "announcements", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "announcements", auth.PermissionWrite))
// Everyone else can do barely anything
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "mytopic", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "mytopic", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "readme", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "readme", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "writeme", auth.PermissionRead))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "writeme", auth.PermissionWrite))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(nil, "announcements", auth.PermissionWrite))
require.Nil(t, a.Authorize(nil, "announcements", auth.PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", auth.PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", auth.PermissionWrite))
require.Nil(t, a.Authorize(nil, "up1234", auth.PermissionWrite)) // Wildcard permission
require.Nil(t, a.Authorize(nil, "up5678", auth.PermissionWrite))
}
func TestSQLiteAuth_AddUser_Invalid(t *testing.T) {
a := newTestAuth(t, false, false)
require.Equal(t, auth.ErrInvalidArgument, a.AddUser(" invalid ", "pass", auth.RoleAdmin))
require.Equal(t, auth.ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
}
func TestSQLiteAuth_AddUser_Timing(t *testing.T) {
a := newTestAuth(t, false, false)
start := time.Now().UnixMilli()
require.Nil(t, a.AddUser("user", "pass", auth.RoleAdmin))
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
}
func TestSQLiteAuth_Authenticate_Timing(t *testing.T) {
a := newTestAuth(t, false, false)
require.Nil(t, a.AddUser("user", "pass", auth.RoleAdmin))
// Timing a correct attempt
start := time.Now().UnixMilli()
_, err := a.Authenticate("user", "pass")
require.Nil(t, err)
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
// Timing an incorrect attempt
start = time.Now().UnixMilli()
_, err = a.Authenticate("user", "INCORRECT")
require.Equal(t, auth.ErrUnauthenticated, err)
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
// Timing a non-existing user attempt
start = time.Now().UnixMilli()
_, err = a.Authenticate("DOES-NOT-EXIST", "hithere")
require.Equal(t, auth.ErrUnauthenticated, err)
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
}
func TestSQLiteAuth_UserManagement(t *testing.T) {
a := newTestAuth(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", auth.RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", auth.RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false))
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(auth.Everyone, "announcements", true, false))
require.Nil(t, a.AllowAccess(auth.Everyone, "everyonewrite", true, true))
// Query user details
phil, err := a.User("phil")
require.Nil(t, err)
require.Equal(t, "phil", phil.Name)
require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
require.Equal(t, auth.RoleAdmin, phil.Role)
require.Equal(t, []auth.Grant{}, phil.Grants)
ben, err := a.User("ben")
require.Nil(t, err)
require.Equal(t, "ben", ben.Name)
require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
require.Equal(t, auth.RoleUser, ben.Role)
require.Equal(t, []auth.Grant{
{"mytopic", true, true},
{"readme", true, false},
{"writeme", false, true},
{"everyonewrite", false, false},
}, ben.Grants)
everyone, err := a.User(auth.Everyone)
require.Nil(t, err)
require.Equal(t, "*", everyone.Name)
require.Equal(t, "", everyone.Hash)
require.Equal(t, auth.RoleAnonymous, everyone.Role)
require.Equal(t, []auth.Grant{
{"announcements", true, false},
{"everyonewrite", true, true},
}, everyone.Grants)
// Ben: Before revoking
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false))
require.Nil(t, a.AllowAccess("ben", "writeme", false, true))
require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionRead))
require.Nil(t, a.Authorize(ben, "mytopic", auth.PermissionWrite))
require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite))
// Revoke access for "ben" to "mytopic", then check again
require.Nil(t, a.ResetAccess("ben", "mytopic"))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "mytopic", auth.PermissionWrite)) // Revoked
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "mytopic", auth.PermissionRead)) // Revoked
require.Nil(t, a.Authorize(ben, "readme", auth.PermissionRead)) // Unchanged
require.Nil(t, a.Authorize(ben, "writeme", auth.PermissionWrite)) // Unchanged
// Revoke rest of the access
require.Nil(t, a.ResetAccess("ben", ""))
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "readme", auth.PermissionRead)) // Revoked
require.Equal(t, auth.ErrUnauthorized, a.Authorize(ben, "wrtiteme", auth.PermissionWrite)) // Revoked
// User list
users, err := a.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "ben", users[1].Name)
require.Equal(t, "*", users[2].Name)
// Remove user
require.Nil(t, a.RemoveUser("ben"))
_, err = a.User("ben")
require.Equal(t, auth.ErrNotFound, err)
users, err = a.Users()
require.Nil(t, err)
require.Equal(t, 2, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "*", users[1].Name)
}
func TestSQLiteAuth_ChangePassword(t *testing.T) {
a := newTestAuth(t, false, false)
require.Nil(t, a.AddUser("phil", "phil", auth.RoleAdmin))
_, err := a.Authenticate("phil", "phil")
require.Nil(t, err)
require.Nil(t, a.ChangePassword("phil", "newpass"))
_, err = a.Authenticate("phil", "phil")
require.Equal(t, auth.ErrUnauthenticated, err)
_, err = a.Authenticate("phil", "newpass")
require.Nil(t, err)
}
func TestSQLiteAuth_ChangeRole(t *testing.T) {
a := newTestAuth(t, false, false)
require.Nil(t, a.AddUser("ben", "ben", auth.RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", true, true))
require.Nil(t, a.AllowAccess("ben", "readme", true, false))
ben, err := a.User("ben")
require.Nil(t, err)
require.Equal(t, auth.RoleUser, ben.Role)
require.Equal(t, 2, len(ben.Grants))
require.Nil(t, a.ChangeRole("ben", auth.RoleAdmin))
ben, err = a.User("ben")
require.Nil(t, err)
require.Equal(t, auth.RoleAdmin, ben.Role)
require.Equal(t, 0, len(ben.Grants))
}
func newTestAuth(t *testing.T, defaultRead, defaultWrite bool) *auth.SQLiteAuthManager {
filename := filepath.Join(t.TempDir(), "user.db")
a, err := auth.NewSQLiteAuthManager(filename, defaultRead, defaultWrite)
require.Nil(t, err)
return a
}