ntfy/user/manager_test.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1333 lines
47 KiB
Go
Raw Permalink Normal View History

2022-12-29 17:09:45 +01:00
package user
2022-01-26 03:57:28 +01:00
import (
2022-12-29 19:08:47 +01:00
"database/sql"
2023-02-12 14:29:44 +01:00
"fmt"
2022-01-26 03:57:28 +01:00
"github.com/stretchr/testify/require"
2023-02-22 04:44:30 +01:00
"github.com/stripe/stripe-go/v74"
2023-01-28 15:03:14 +01:00
"golang.org/x/crypto/bcrypt"
2023-11-17 02:54:58 +01:00
"heckel.io/ntfy/v2/util"
2023-01-29 02:29:06 +01:00
"net/netip"
2022-01-26 03:57:28 +01:00
"path/filepath"
"strings"
"testing"
"time"
)
2022-02-08 04:09:31 +01:00
const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this should also run on a Raspberry Pi without massive resources
2022-12-28 19:46:18 +01:00
func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
2023-01-28 15:03:14 +01:00
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("john", "john", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair!
require.Nil(t, a.AllowAccess("john", "*", PermissionRead))
require.Nil(t, a.AllowAccess("john", "mytopic*", PermissionReadWrite))
require.Nil(t, a.AllowAccess("john", "mytopic_ro*", PermissionRead))
require.Nil(t, a.AllowAccess("john", "mytopic_deny*", PermissionDenyAll))
require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead))
require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite))
require.Nil(t, a.AllowAccess(Everyone, "up*", PermissionWrite)) // Everyone can write to /up*
2022-01-26 03:57:28 +01:00
phil, err := a.Authenticate("phil", "phil")
require.Nil(t, err)
require.Equal(t, "phil", phil.Name)
2022-02-08 04:09:31 +01:00
require.True(t, strings.HasPrefix(phil.Hash, "$2a$10$"))
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleAdmin, phil.Role)
philGrants, err := a.Grants("phil")
require.Nil(t, err)
require.Equal(t, []Grant{}, philGrants)
2022-01-26 03:57:28 +01:00
ben, err := a.Authenticate("ben", "ben")
require.Nil(t, err)
require.Equal(t, "ben", ben.Name)
2022-02-08 04:09:31 +01:00
require.True(t, strings.HasPrefix(ben.Hash, "$2a$10$"))
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleUser, ben.Role)
benGrants, err := a.Grants("ben")
require.Nil(t, err)
2022-12-29 17:09:45 +01:00
require.Equal(t, []Grant{
{"everyonewrite", PermissionDenyAll},
{"mytopic", PermissionReadWrite},
{"writeme", PermissionWrite},
{"readme", PermissionRead},
}, benGrants)
2022-01-26 03:57:28 +01:00
john, err := a.Authenticate("john", "john")
require.Nil(t, err)
require.Equal(t, "john", john.Name)
require.True(t, strings.HasPrefix(john.Hash, "$2a$10$"))
require.Equal(t, RoleUser, john.Role)
johnGrants, err := a.Grants("john")
require.Nil(t, err)
require.Equal(t, []Grant{
{"mytopic_deny*", PermissionDenyAll},
{"mytopic_ro*", PermissionRead},
{"mytopic*", PermissionReadWrite},
{"*", PermissionRead},
}, johnGrants)
2022-01-26 03:57:28 +01:00
notben, err := a.Authenticate("ben", "this is wrong")
require.Nil(t, notben)
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthenticated, err)
2022-01-26 03:57:28 +01:00
// Admin can do everything
2022-12-29 17:09:45 +01:00
require.Nil(t, a.Authorize(phil, "sometopic", PermissionWrite))
require.Nil(t, a.Authorize(phil, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(phil, "readme", PermissionWrite))
require.Nil(t, a.Authorize(phil, "writeme", PermissionWrite))
require.Nil(t, a.Authorize(phil, "announcements", PermissionWrite))
require.Nil(t, a.Authorize(phil, "everyonewrite", PermissionWrite))
2022-01-26 03:57:28 +01:00
// User cannot do everything
2022-12-29 17:09:45 +01:00
require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "writeme", PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "everyonewrite", PermissionWrite))
require.Nil(t, a.Authorize(ben, "announcements", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "announcements", PermissionWrite))
2022-01-26 03:57:28 +01:00
// User john should have
// "deny" to mytopic_deny*,
// "ro" to mytopic_ro*,
// "rw" to mytopic*,
// "ro" to the rest
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_deny_case", PermissionWrite))
require.Nil(t, a.Authorize(john, "mytopic_ro_test_case", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(john, "mytopic_ro_test_case", PermissionWrite))
require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionRead))
require.Nil(t, a.Authorize(john, "mytopic_case1", PermissionWrite))
require.Nil(t, a.Authorize(john, "readme", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(john, "writeme", PermissionWrite))
2022-01-26 03:57:28 +01:00
// Everyone else can do barely anything
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "sometopicnotinthelist", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopic", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "readme", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "writeme", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "announcements", PermissionWrite))
require.Nil(t, a.Authorize(nil, "announcements", PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionRead))
require.Nil(t, a.Authorize(nil, "everyonewrite", PermissionWrite))
require.Nil(t, a.Authorize(nil, "up1234", PermissionWrite)) // Wildcard permission
require.Nil(t, a.Authorize(nil, "up5678", PermissionWrite))
2022-01-26 03:57:28 +01:00
}
func TestManager_Access_Order_LengthWriteRead(t *testing.T) {
// This test validates issue #914 / #917, i.e. that write permissions are prioritized over read permissions,
// and longer ACL rules are prioritized as well.
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "test*", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "*", PermissionRead))
ben, err := a.Authenticate("ben", "ben")
require.Nil(t, err)
require.Nil(t, a.Authorize(ben, "any-topic-can-be-read", PermissionRead))
require.Nil(t, a.Authorize(ben, "this-too", PermissionRead))
require.Nil(t, a.Authorize(ben, "test123", PermissionWrite))
}
2022-12-28 19:46:18 +01:00
func TestManager_AddUser_Invalid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
2022-01-26 03:57:28 +01:00
}
2022-12-28 19:46:18 +01:00
func TestManager_AddUser_Timing(t *testing.T) {
2023-01-28 15:03:14 +01:00
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
2022-01-26 03:57:28 +01:00
start := time.Now().UnixMilli()
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
2022-02-08 04:09:31 +01:00
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
2022-01-26 03:57:28 +01:00
}
2023-02-12 04:14:09 +01:00
func TestManager_AddUser_And_Query(t *testing.T) {
a := newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.ChangeBilling("user", &Billing{
StripeCustomerID: "acct_123",
StripeSubscriptionID: "sub_123",
2023-02-22 04:44:30 +01:00
StripeSubscriptionStatus: stripe.SubscriptionStatusActive,
StripeSubscriptionInterval: stripe.PriceRecurringIntervalMonth,
2023-02-12 04:14:09 +01:00
StripeSubscriptionPaidUntil: time.Now().Add(time.Hour),
StripeSubscriptionCancelAt: time.Unix(0, 0),
}))
u, err := a.User("user")
require.Nil(t, err)
require.Equal(t, "user", u.Name)
u2, err := a.UserByID(u.ID)
require.Nil(t, err)
require.Equal(t, u.Name, u2.Name)
u3, err := a.UserByStripeCustomer("acct_123")
require.Nil(t, err)
require.Equal(t, u.ID, u3.ID)
}
2023-01-23 04:21:30 +01:00
func TestManager_MarkUserRemoved_RemoveDeletedUsers(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
// Create user, add reservations and token
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
require.Nil(t, a.AddReservation("user", "mytopic", PermissionRead))
u, err := a.User("user")
require.Nil(t, err)
require.False(t, u.Deleted)
2023-01-29 02:29:06 +01:00
token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified())
2023-01-23 04:21:30 +01:00
require.Nil(t, err)
u, err = a.Authenticate("user", "pass")
require.Nil(t, err)
_, err = a.AuthenticateToken(token.Value)
require.Nil(t, err)
reservations, err := a.Reservations("user")
require.Nil(t, err)
require.Equal(t, 1, len(reservations))
// Mark deleted: cannot auth anymore, and all reservations are gone
require.Nil(t, a.MarkUserRemoved(u))
_, err = a.Authenticate("user", "pass")
require.Equal(t, ErrUnauthenticated, err)
_, err = a.AuthenticateToken(token.Value)
require.Equal(t, ErrUnauthenticated, err)
reservations, err = a.Reservations("user")
require.Nil(t, err)
require.Equal(t, 0, len(reservations))
// Make sure user is still there
u, err = a.User("user")
require.Nil(t, err)
require.True(t, u.Deleted)
_, err = a.db.Exec("UPDATE user SET deleted = ? WHERE id = ?", time.Now().Add(-1*(userHardDeleteAfterDuration+time.Hour)).Unix(), u.ID)
require.Nil(t, err)
require.Nil(t, a.RemoveDeletedUsers())
_, err = a.User("user")
require.Equal(t, ErrUserNotFound, err)
}
func TestManager_CreateToken_Only_Lower(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
// Create user, add reservations and token
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
u, err := a.User("user")
require.Nil(t, err)
token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified())
require.Nil(t, err)
require.Equal(t, token.Value, strings.ToLower(token.Value))
}
2022-12-28 19:46:18 +01:00
func TestManager_UserManagement(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair!
require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead))
require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite))
2022-01-26 03:57:28 +01:00
// Query user details
phil, err := a.User("phil")
require.Nil(t, err)
require.Equal(t, "phil", phil.Name)
2023-01-28 15:03:14 +01:00
require.True(t, strings.HasPrefix(phil.Hash, "$2a$04$")) // Min cost for testing
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleAdmin, phil.Role)
philGrants, err := a.Grants("phil")
require.Nil(t, err)
require.Equal(t, []Grant{}, philGrants)
2022-01-26 03:57:28 +01:00
ben, err := a.User("ben")
require.Nil(t, err)
require.Equal(t, "ben", ben.Name)
2023-01-28 15:03:14 +01:00
require.True(t, strings.HasPrefix(ben.Hash, "$2a$04$")) // Min cost for testing
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleUser, ben.Role)
benGrants, err := a.Grants("ben")
require.Nil(t, err)
2022-12-29 17:09:45 +01:00
require.Equal(t, []Grant{
{"everyonewrite", PermissionDenyAll},
{"mytopic", PermissionReadWrite},
{"writeme", PermissionWrite},
{"readme", PermissionRead},
}, benGrants)
2022-01-26 03:57:28 +01:00
2022-12-29 17:09:45 +01:00
everyone, err := a.User(Everyone)
2022-01-26 03:57:28 +01:00
require.Nil(t, err)
require.Equal(t, "*", everyone.Name)
require.Equal(t, "", everyone.Hash)
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleAnonymous, everyone.Role)
everyoneGrants, err := a.Grants(Everyone)
require.Nil(t, err)
2022-12-29 17:09:45 +01:00
require.Equal(t, []Grant{
{"everyonewrite", PermissionReadWrite},
{"announcements", PermissionRead},
}, everyoneGrants)
2022-01-26 03:57:28 +01:00
// Ben: Before revoking
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) // Overwrite!
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
2022-12-29 17:09:45 +01:00
require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite))
2022-01-26 03:57:28 +01:00
// Revoke access for "ben" to "mytopic", then check again
require.Nil(t, a.ResetAccess("ben", "mytopic"))
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionWrite)) // Revoked
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "mytopic", PermissionRead)) // Revoked
require.Nil(t, a.Authorize(ben, "readme", PermissionRead)) // Unchanged
require.Nil(t, a.Authorize(ben, "writeme", PermissionWrite)) // Unchanged
2022-01-26 03:57:28 +01:00
// Revoke rest of the access
require.Nil(t, a.ResetAccess("ben", ""))
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "readme", PermissionRead)) // Revoked
require.Equal(t, ErrUnauthorized, a.Authorize(ben, "wrtiteme", PermissionWrite)) // Revoked
2022-01-26 03:57:28 +01:00
// 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")
2023-01-14 12:43:44 +01:00
require.Equal(t, ErrUserNotFound, err)
2022-01-26 03:57:28 +01:00
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)
}
2022-12-28 19:46:18 +01:00
func TestManager_ChangePassword(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
2022-01-26 03:57:28 +01:00
_, err := a.Authenticate("phil", "phil")
require.Nil(t, err)
require.Nil(t, a.ChangePassword("phil", "newpass"))
_, err = a.Authenticate("phil", "phil")
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthenticated, err)
2022-01-26 03:57:28 +01:00
_, err = a.Authenticate("phil", "newpass")
require.Nil(t, err)
}
2022-12-28 19:46:18 +01:00
func TestManager_ChangeRole(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
2022-01-26 03:57:28 +01:00
ben, err := a.User("ben")
require.Nil(t, err)
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleUser, ben.Role)
benGrants, err := a.Grants("ben")
require.Nil(t, err)
require.Equal(t, 2, len(benGrants))
2022-01-26 03:57:28 +01:00
2022-12-29 17:09:45 +01:00
require.Nil(t, a.ChangeRole("ben", RoleAdmin))
2022-01-26 03:57:28 +01:00
ben, err = a.User("ben")
require.Nil(t, err)
2022-12-29 17:09:45 +01:00
require.Equal(t, RoleAdmin, ben.Role)
benGrants, err = a.Grants("ben")
require.Nil(t, err)
require.Equal(t, 0, len(benGrants))
2022-01-26 03:57:28 +01:00
}
func TestManager_Reservations(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-02-12 04:14:09 +01:00
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2023-08-18 22:44:52 +02:00
require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll))
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
reservations, err := a.Reservations("ben")
require.Nil(t, err)
require.Equal(t, 2, len(reservations))
require.Equal(t, Reservation{
Topic: "readme",
Owner: PermissionReadWrite,
Everyone: PermissionRead,
}, reservations[0])
require.Equal(t, Reservation{
2023-08-18 22:44:52 +02:00
Topic: "ztopic_",
Owner: PermissionReadWrite,
Everyone: PermissionDenyAll,
}, reservations[1])
2023-02-12 04:14:09 +01:00
b, err := a.HasReservation("ben", "readme")
require.Nil(t, err)
require.True(t, b)
2023-08-18 22:44:52 +02:00
b, err = a.HasReservation("ben", "ztopic_")
require.Nil(t, err)
require.True(t, b)
b, err = a.HasReservation("ben", "ztopicX") // _ != X (used to be a SQL wildcard issue)
require.Nil(t, err)
require.False(t, b)
2023-02-12 04:14:09 +01:00
b, err = a.HasReservation("notben", "readme")
require.Nil(t, err)
require.False(t, b)
b, err = a.HasReservation("ben", "something-else")
require.Nil(t, err)
require.False(t, b)
count, err := a.ReservationsCount("ben")
require.Nil(t, err)
require.Equal(t, int64(2), count)
count, err = a.ReservationsCount("phil")
require.Nil(t, err)
require.Equal(t, int64(0), count)
err = a.AllowReservation("phil", "readme")
require.Equal(t, errTopicOwnedByOthers, err)
2023-08-18 22:44:52 +02:00
err = a.AllowReservation("phil", "ztopic_")
require.Equal(t, errTopicOwnedByOthers, err)
err = a.AllowReservation("phil", "ztopicX")
require.Nil(t, err)
2023-02-12 04:14:09 +01:00
err = a.AllowReservation("phil", "not-reserved")
require.Nil(t, err)
2023-02-12 14:29:44 +01:00
// Now remove them again
2023-08-18 22:44:52 +02:00
require.Nil(t, a.RemoveReservations("ben", "ztopic_", "readme"))
2023-02-12 14:29:44 +01:00
count, err = a.ReservationsCount("ben")
require.Nil(t, err)
require.Equal(t, int64(0), count)
}
2023-01-09 21:40:46 +01:00
func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-02-07 04:38:22 +01:00
require.Nil(t, a.AddTier(&Tier{
2023-01-09 21:40:46 +01:00
Code: "pro",
Name: "ntfy Pro",
2023-02-22 04:44:30 +01:00
StripeMonthlyPriceID: "price123",
2023-01-27 04:57:18 +01:00
MessageLimit: 5_000,
MessageExpiryDuration: 3 * 24 * time.Hour,
EmailLimit: 50,
ReservationLimit: 5,
2023-01-09 21:40:46 +01:00
AttachmentFileSizeLimit: 52428800,
AttachmentTotalSizeLimit: 524288000,
AttachmentExpiryDuration: 24 * time.Hour,
}))
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2023-01-09 21:40:46 +01:00
require.Nil(t, a.ChangeTier("ben", "pro"))
require.Nil(t, a.AddReservation("ben", "mytopic", PermissionDenyAll))
2023-01-09 21:40:46 +01:00
ben, err := a.User("ben")
require.Nil(t, err)
require.Equal(t, RoleUser, ben.Role)
require.Equal(t, "pro", ben.Tier.Code)
2023-01-27 04:57:18 +01:00
require.Equal(t, int64(5000), ben.Tier.MessageLimit)
require.Equal(t, 3*24*time.Hour, ben.Tier.MessageExpiryDuration)
require.Equal(t, int64(50), ben.Tier.EmailLimit)
require.Equal(t, int64(5), ben.Tier.ReservationLimit)
2023-01-09 21:40:46 +01:00
require.Equal(t, int64(52428800), ben.Tier.AttachmentFileSizeLimit)
require.Equal(t, int64(524288000), ben.Tier.AttachmentTotalSizeLimit)
require.Equal(t, 24*time.Hour, ben.Tier.AttachmentExpiryDuration)
benGrants, err := a.Grants("ben")
require.Nil(t, err)
require.Equal(t, 1, len(benGrants))
require.Equal(t, PermissionReadWrite, benGrants[0].Allow)
everyoneGrants, err := a.Grants(Everyone)
require.Nil(t, err)
require.Equal(t, 1, len(everyoneGrants))
require.Equal(t, PermissionDenyAll, everyoneGrants[0].Allow)
benReservations, err := a.Reservations("ben")
require.Nil(t, err)
require.Equal(t, 1, len(benReservations))
require.Equal(t, "mytopic", benReservations[0].Topic)
require.Equal(t, PermissionReadWrite, benReservations[0].Owner)
require.Equal(t, PermissionDenyAll, benReservations[0].Everyone)
2023-01-09 21:40:46 +01:00
// Switch to admin, this should remove all grants and owned ACL entries
require.Nil(t, a.ChangeRole("ben", RoleAdmin))
benGrants, err = a.Grants("ben")
require.Nil(t, err)
require.Equal(t, 0, len(benGrants))
everyoneGrants, err = a.Grants(Everyone)
require.Nil(t, err)
require.Equal(t, 0, len(everyoneGrants))
}
2022-12-28 19:46:18 +01:00
func TestManager_Token_Valid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-28 19:46:18 +01:00
u, err := a.User("ben")
require.Nil(t, err)
// Create token for user
2023-01-29 02:29:06 +01:00
token, err := a.CreateToken(u.ID, "some label", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
2022-12-28 21:51:09 +01:00
require.Nil(t, err)
2022-12-28 19:46:18 +01:00
require.NotEmpty(t, token.Value)
2023-01-28 05:10:59 +01:00
require.Equal(t, "some label", token.Label)
2022-12-28 19:46:18 +01:00
require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires.Unix())
u2, err := a.AuthenticateToken(token.Value)
require.Nil(t, err)
require.Equal(t, u.Name, u2.Name)
require.Equal(t, token.Value, u2.Token)
2023-01-28 05:10:59 +01:00
token2, err := a.Token(u.ID, token.Value)
require.Nil(t, err)
require.Equal(t, token.Value, token2.Value)
require.Equal(t, "some label", token2.Label)
2023-02-12 04:14:09 +01:00
tokens, err := a.Tokens(u.ID)
require.Nil(t, err)
require.Equal(t, 1, len(tokens))
require.Equal(t, "some label", tokens[0].Label)
tokens, err = a.Tokens("u_notauser")
require.Nil(t, err)
require.Equal(t, 0, len(tokens))
2022-12-28 19:46:18 +01:00
// Remove token and auth again
2023-01-28 05:10:59 +01:00
require.Nil(t, a.RemoveToken(u2.ID, u2.Token))
2022-12-28 19:46:18 +01:00
u3, err := a.AuthenticateToken(token.Value)
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthenticated, err)
2022-12-28 19:46:18 +01:00
require.Nil(t, u3)
2023-02-12 04:14:09 +01:00
tokens, err = a.Tokens(u.ID)
require.Nil(t, err)
require.Equal(t, 0, len(tokens))
2022-12-28 19:46:18 +01:00
}
func TestManager_Token_Invalid(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-28 19:46:18 +01:00
u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
require.Nil(t, u)
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthenticated, err)
2022-12-28 19:46:18 +01:00
u, err = a.AuthenticateToken("not long enough anyway")
require.Nil(t, u)
2022-12-29 17:09:45 +01:00
require.Equal(t, ErrUnauthenticated, err)
2022-12-28 19:46:18 +01:00
}
2023-02-12 04:14:09 +01:00
func TestManager_Token_NotFound(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
_, err := a.Token("u_bla", "notfound")
require.Equal(t, ErrTokenNotFound, err)
}
2022-12-29 17:09:45 +01:00
func TestManager_Token_Expire(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-29 17:09:45 +01:00
u, err := a.User("ben")
require.Nil(t, err)
// Create tokens for user
2023-01-29 02:29:06 +01:00
token1, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
2022-12-29 17:09:45 +01:00
require.Nil(t, err)
require.NotEmpty(t, token1.Value)
require.True(t, time.Now().Add(71*time.Hour).Unix() < token1.Expires.Unix())
2023-01-29 02:29:06 +01:00
token2, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
2022-12-29 17:09:45 +01:00
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())
}
2022-12-31 16:16:14 +01:00
func TestManager_Token_Extend(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-31 16:16:14 +01:00
// Try to extend token for user without token
u, err := a.User("ben")
require.Nil(t, err)
2023-01-28 05:10:59 +01:00
_, err = a.ChangeToken(u.ID, u.Token, util.String("some label"), util.Time(time.Now().Add(time.Hour)))
2022-12-31 16:16:14 +01:00
require.Equal(t, errNoTokenProvided, err)
// Create token for user
2023-01-29 02:29:06 +01:00
token, err := a.CreateToken(u.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
2022-12-31 16:16:14 +01:00
require.Nil(t, err)
require.NotEmpty(t, token.Value)
userWithToken, err := a.AuthenticateToken(token.Value)
require.Nil(t, err)
2023-01-28 05:10:59 +01:00
extendedToken, err := a.ChangeToken(userWithToken.ID, userWithToken.Token, util.String("changed label"), util.Time(time.Now().Add(100*time.Hour)))
2022-12-31 16:16:14 +01:00
require.Nil(t, err)
require.Equal(t, token.Value, extendedToken.Value)
2023-01-28 05:10:59 +01:00
require.Equal(t, "changed label", extendedToken.Label)
2022-12-31 16:16:14 +01:00
require.True(t, token.Expires.Unix() < extendedToken.Expires.Unix())
2023-01-28 05:10:59 +01:00
require.True(t, time.Now().Add(99*time.Hour).Unix() < extendedToken.Expires.Unix())
2022-12-31 16:16:14 +01:00
}
2023-01-06 02:22:34 +01:00
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
2023-08-17 21:42:40 +02:00
// Tests that tokens are automatically deleted when the maximum number of tokens is reached
2023-01-06 02:22:34 +01:00
a := newTestManager(t, PermissionDenyAll)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2023-08-17 21:42:40 +02:00
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
2023-01-06 02:22:34 +01:00
2023-08-17 21:42:40 +02:00
ben, err := a.User("ben")
2023-01-06 02:22:34 +01:00
require.Nil(t, err)
2023-08-17 21:42:40 +02:00
phil, err := a.User("phil")
require.Nil(t, err)
// Create 2 tokens for phil
philTokens := make([]string, 0)
token, err := a.CreateToken(phil.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
require.Nil(t, err)
require.NotEmpty(t, token.Value)
philTokens = append(philTokens, token.Value)
token, err = a.CreateToken(phil.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
require.Nil(t, err)
require.NotEmpty(t, token.Value)
philTokens = append(philTokens, token.Value)
// Create 22 tokens for ben (only 20 allowed!)
2023-01-06 02:22:34 +01:00
baseTime := time.Now().Add(24 * time.Hour)
2023-08-17 21:42:40 +02:00
benTokens := make([]string, 0)
for i := 0; i < 22; i++ { //
token, err := a.CreateToken(ben.ID, "", time.Now().Add(72*time.Hour), netip.IPv4Unspecified())
2023-01-06 02:22:34 +01:00
require.Nil(t, err)
require.NotEmpty(t, token.Value)
2023-08-17 21:42:40 +02:00
benTokens = append(benTokens, token.Value)
2023-01-06 02:22:34 +01:00
// Manually modify expiry date to avoid sorting issues (this is a hack)
_, err = a.db.Exec(`UPDATE user_token SET expires=? WHERE token=?`, baseTime.Add(time.Duration(i)*time.Minute).Unix(), token.Value)
require.Nil(t, err)
}
2023-08-17 21:42:40 +02:00
// Ben: The first 2 tokens should have been wiped and should not work anymore!
_, err = a.AuthenticateToken(benTokens[0])
2023-01-06 02:22:34 +01:00
require.Equal(t, ErrUnauthenticated, err)
2023-08-17 21:42:40 +02:00
_, err = a.AuthenticateToken(benTokens[1])
2023-01-06 02:22:34 +01:00
require.Equal(t, ErrUnauthenticated, err)
2023-08-17 21:42:40 +02:00
// Ben: The other tokens should still work
2023-01-30 03:51:49 +01:00
for i := 2; i < 22; i++ {
2023-08-17 21:42:40 +02:00
userWithToken, err := a.AuthenticateToken(benTokens[i])
require.Nil(t, err, "token[%d]=%s failed", i, benTokens[i])
2023-01-06 02:22:34 +01:00
require.Equal(t, "ben", userWithToken.Name)
2023-08-17 21:42:40 +02:00
require.Equal(t, benTokens[i], userWithToken.Token)
2023-01-06 02:22:34 +01:00
}
2023-08-17 21:42:40 +02:00
// Phil: All tokens should still work
for i := 0; i < 2; i++ {
userWithToken, err := a.AuthenticateToken(philTokens[i])
require.Nil(t, err, "token[%d]=%s failed", i, philTokens[i])
require.Equal(t, "phil", userWithToken.Name)
require.Equal(t, philTokens[i], userWithToken.Token)
}
var benCount int
rows, err := a.db.Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, ben.ID)
require.Nil(t, err)
require.True(t, rows.Next())
require.Nil(t, rows.Scan(&benCount))
require.Equal(t, 20, benCount)
var philCount int
rows, err = a.db.Query(`SELECT COUNT(*) FROM user_token WHERE user_id=?`, phil.ID)
2023-01-06 02:22:34 +01:00
require.Nil(t, err)
require.True(t, rows.Next())
2023-08-17 21:42:40 +02:00
require.Nil(t, rows.Scan(&philCount))
require.Equal(t, 2, philCount)
2023-01-06 02:22:34 +01:00
}
2023-02-12 04:14:09 +01:00
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
2023-01-28 15:03:14 +01:00
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
2022-12-29 17:09:45 +01:00
require.Nil(t, err)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-29 17:09:45 +01:00
// 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)
2023-02-09 21:24:12 +01:00
a.EnqueueUserStats(u.ID, &Stats{
2023-01-27 15:59:16 +01:00
Messages: 11,
Emails: 2,
})
2022-12-29 17:09:45 +01:00
// 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)
2023-02-12 04:14:09 +01:00
// Now reset stats (enqueued stats will be thrown out)
a.EnqueueUserStats(u.ID, &Stats{
Messages: 99,
Emails: 23,
})
require.Nil(t, a.ResetStats())
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)
}
func TestManager_EnqueueTokenUpdate(t *testing.T) {
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
// Create user and token
u, err := a.User("ben")
require.Nil(t, err)
token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified())
require.Nil(t, err)
// Queue token update
a.EnqueueTokenUpdate(token.Value, &TokenUpdate{
LastAccess: time.Unix(111, 0).UTC(),
LastOrigin: netip.MustParseAddr("1.2.3.3"),
})
// Token has not changed yet.
token2, err := a.Token(u.ID, token.Value)
require.Nil(t, err)
require.Equal(t, token.LastAccess.Unix(), token2.LastAccess.Unix())
require.Equal(t, token.LastOrigin, token2.LastOrigin)
// After a second or so they should be persisted
time.Sleep(time.Second)
token3, err := a.Token(u.ID, token.Value)
require.Nil(t, err)
require.Equal(t, time.Unix(111, 0).UTC().Unix(), token3.LastAccess.Unix())
require.Equal(t, netip.MustParseAddr("1.2.3.3"), token3.LastOrigin)
2022-12-29 17:09:45 +01:00
}
2022-12-31 16:16:14 +01:00
func TestManager_ChangeSettings(t *testing.T) {
2023-01-28 15:03:14 +01:00
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
2022-12-31 16:16:14 +01:00
require.Nil(t, err)
2023-01-23 04:21:30 +01:00
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
2022-12-31 16:16:14 +01:00
// No settings
u, err := a.User("ben")
require.Nil(t, err)
2023-01-10 03:53:21 +01:00
require.Nil(t, u.Prefs.Subscriptions)
require.Nil(t, u.Prefs.Notification)
require.Nil(t, u.Prefs.Language)
2022-12-31 16:16:14 +01:00
// Save with new settings
2023-02-09 21:24:12 +01:00
prefs := &Prefs{
Language: util.String("de"),
2022-12-31 16:16:14 +01:00
Notification: &NotificationPrefs{
Sound: util.String("ding"),
MinPriority: util.Int(2),
2022-12-31 16:16:14 +01:00
},
Subscriptions: []*Subscription{
{
BaseURL: "https://ntfy.sh",
Topic: "mytopic",
DisplayName: util.String("My Topic"),
2022-12-31 16:16:14 +01:00
},
},
}
2023-02-09 21:24:12 +01:00
require.Nil(t, a.ChangeSettings(u.ID, prefs))
2022-12-31 16:16:14 +01:00
// Read again
u, err = a.User("ben")
require.Nil(t, err)
require.Equal(t, util.String("de"), u.Prefs.Language)
require.Equal(t, util.String("ding"), u.Prefs.Notification.Sound)
require.Equal(t, util.Int(2), u.Prefs.Notification.MinPriority)
require.Nil(t, u.Prefs.Notification.DeleteAfter)
2022-12-31 16:16:14 +01:00
require.Equal(t, "https://ntfy.sh", u.Prefs.Subscriptions[0].BaseURL)
require.Equal(t, "mytopic", u.Prefs.Subscriptions[0].Topic)
require.Equal(t, util.String("My Topic"), u.Prefs.Subscriptions[0].DisplayName)
2022-12-31 16:16:14 +01:00
}
2023-02-12 14:29:44 +01:00
func TestManager_Tier_Create_Update_List_Delete(t *testing.T) {
2023-01-29 02:29:06 +01:00
a := newTestManager(t, PermissionDenyAll)
// Create tier and user
2023-02-12 14:29:44 +01:00
require.Nil(t, a.AddTier(&Tier{
Code: "supporter",
Name: "Supporter",
MessageLimit: 1,
MessageExpiryDuration: time.Second,
EmailLimit: 1,
ReservationLimit: 1,
AttachmentFileSizeLimit: 1,
AttachmentTotalSizeLimit: 1,
AttachmentExpiryDuration: time.Second,
AttachmentBandwidthLimit: 1,
2023-02-22 04:44:30 +01:00
StripeMonthlyPriceID: "price_1",
2023-02-12 14:29:44 +01:00
}))
2023-02-07 04:38:22 +01:00
require.Nil(t, a.AddTier(&Tier{
2023-01-29 02:29:06 +01:00
Code: "pro",
Name: "Pro",
MessageLimit: 123,
MessageExpiryDuration: 86400 * time.Second,
EmailLimit: 32,
ReservationLimit: 2,
AttachmentFileSizeLimit: 1231231,
AttachmentTotalSizeLimit: 123123,
AttachmentExpiryDuration: 10800 * time.Second,
AttachmentBandwidthLimit: 21474836480,
2023-02-22 04:44:30 +01:00
StripeMonthlyPriceID: "price_2",
2023-01-29 02:29:06 +01:00
}))
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.ChangeTier("phil", "pro"))
ti, err := a.Tier("pro")
require.Nil(t, err)
u, err := a.User("phil")
require.Nil(t, err)
// These are populated by different SQL queries
require.Equal(t, ti, u.Tier)
// Fields
require.True(t, strings.HasPrefix(ti.ID, "ti_"))
require.Equal(t, "pro", ti.Code)
require.Equal(t, "Pro", ti.Name)
require.Equal(t, int64(123), ti.MessageLimit)
require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration)
require.Equal(t, int64(32), ti.EmailLimit)
require.Equal(t, int64(2), ti.ReservationLimit)
require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit)
require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit)
require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration)
require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit)
2023-02-22 04:44:30 +01:00
require.Equal(t, "price_2", ti.StripeMonthlyPriceID)
2023-02-12 14:29:44 +01:00
// Update tier
ti.EmailLimit = 999999
require.Nil(t, a.UpdateTier(ti))
// List tiers
tiers, err := a.Tiers()
require.Nil(t, err)
require.Equal(t, 2, len(tiers))
ti = tiers[0]
require.Equal(t, "supporter", ti.Code)
require.Equal(t, "Supporter", ti.Name)
require.Equal(t, int64(1), ti.MessageLimit)
require.Equal(t, time.Second, ti.MessageExpiryDuration)
require.Equal(t, int64(1), ti.EmailLimit)
require.Equal(t, int64(1), ti.ReservationLimit)
require.Equal(t, int64(1), ti.AttachmentFileSizeLimit)
require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit)
require.Equal(t, time.Second, ti.AttachmentExpiryDuration)
require.Equal(t, int64(1), ti.AttachmentBandwidthLimit)
2023-02-22 04:44:30 +01:00
require.Equal(t, "price_1", ti.StripeMonthlyPriceID)
2023-02-12 14:29:44 +01:00
ti = tiers[1]
require.Equal(t, "pro", ti.Code)
require.Equal(t, "Pro", ti.Name)
require.Equal(t, int64(123), ti.MessageLimit)
require.Equal(t, 86400*time.Second, ti.MessageExpiryDuration)
require.Equal(t, int64(999999), ti.EmailLimit) // Updatedd!
require.Equal(t, int64(2), ti.ReservationLimit)
require.Equal(t, int64(1231231), ti.AttachmentFileSizeLimit)
require.Equal(t, int64(123123), ti.AttachmentTotalSizeLimit)
require.Equal(t, 10800*time.Second, ti.AttachmentExpiryDuration)
require.Equal(t, int64(21474836480), ti.AttachmentBandwidthLimit)
2023-02-22 04:44:30 +01:00
require.Equal(t, "price_2", ti.StripeMonthlyPriceID)
2023-02-12 14:29:44 +01:00
ti, err = a.TierByStripePrice("price_1")
require.Nil(t, err)
require.Equal(t, "supporter", ti.Code)
require.Equal(t, "Supporter", ti.Name)
require.Equal(t, int64(1), ti.MessageLimit)
require.Equal(t, time.Second, ti.MessageExpiryDuration)
require.Equal(t, int64(1), ti.EmailLimit)
require.Equal(t, int64(1), ti.ReservationLimit)
require.Equal(t, int64(1), ti.AttachmentFileSizeLimit)
require.Equal(t, int64(1), ti.AttachmentTotalSizeLimit)
require.Equal(t, time.Second, ti.AttachmentExpiryDuration)
require.Equal(t, int64(1), ti.AttachmentBandwidthLimit)
2023-02-22 04:44:30 +01:00
require.Equal(t, "price_1", ti.StripeMonthlyPriceID)
2023-02-12 14:29:44 +01:00
// Cannot remove tier, since user has this tier
require.Error(t, a.RemoveTier("pro"))
// CAN remove this tier
require.Nil(t, a.RemoveTier("supporter"))
tiers, err = a.Tiers()
require.Nil(t, err)
require.Equal(t, 1, len(tiers))
require.Equal(t, "pro", tiers[0].Code)
require.Equal(t, "pro", tiers[0].Code)
2023-01-29 02:29:06 +01:00
}
func TestAccount_Tier_Create_With_ID(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
2023-02-07 04:38:22 +01:00
require.Nil(t, a.AddTier(&Tier{
2023-01-29 02:29:06 +01:00
ID: "ti_123",
Code: "pro",
}))
ti, err := a.Tier("pro")
require.Nil(t, err)
require.Equal(t, "ti_123", ti.ID)
}
2023-02-12 14:29:44 +01:00
func TestManager_Tier_Change_And_Reset(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
// Create tier and user
require.Nil(t, a.AddTier(&Tier{
Code: "supporter",
Name: "Supporter",
ReservationLimit: 3,
}))
require.Nil(t, a.AddTier(&Tier{
Code: "pro",
Name: "Pro",
ReservationLimit: 4,
}))
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.ChangeTier("phil", "pro"))
// Add 10 reservations (pro tier allows that)
for i := 0; i < 4; i++ {
require.Nil(t, a.AddReservation("phil", fmt.Sprintf("topic%d", i), PermissionWrite))
}
// Downgrading will not work (too many reservations)
require.Equal(t, ErrTooManyReservations, a.ChangeTier("phil", "supporter"))
// Downgrade after removing a reservation
require.Nil(t, a.RemoveReservations("phil", "topic0"))
require.Nil(t, a.ChangeTier("phil", "supporter"))
// Resetting will not work (too many reservations)
require.Equal(t, ErrTooManyReservations, a.ResetTier("phil"))
// Resetting after removing all reservations
require.Nil(t, a.RemoveReservations("phil", "topic1", "topic2", "topic3"))
require.Nil(t, a.ResetTier("phil"))
}
2023-05-17 16:39:15 +02:00
func TestUser_PhoneNumberAddListRemove(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
phil, err := a.User("phil")
require.Nil(t, err)
require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890"))
phoneNumbers, err := a.PhoneNumbers(phil.ID)
require.Nil(t, err)
require.Equal(t, 1, len(phoneNumbers))
require.Equal(t, "+1234567890", phoneNumbers[0])
require.Nil(t, a.RemovePhoneNumber(phil.ID, "+1234567890"))
phoneNumbers, err = a.PhoneNumbers(phil.ID)
require.Nil(t, err)
require.Equal(t, 0, len(phoneNumbers))
2023-05-17 17:19:48 +02:00
// Paranoia check: We do NOT want to keep phone numbers in there
rows, err := a.db.Query(`SELECT * FROM user_phone`)
require.Nil(t, err)
require.False(t, rows.Next())
require.Nil(t, rows.Close())
2023-05-17 16:39:15 +02:00
}
func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
phil, err := a.User("phil")
require.Nil(t, err)
ben, err := a.User("ben")
require.Nil(t, err)
require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890"))
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
}
2023-08-18 22:44:52 +02:00
func TestManager_Topic_Wildcard_With_Asterisk_Underscore(t *testing.T) {
f := filepath.Join(t.TempDir(), "user.db")
a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AllowAccess(Everyone, "*_", PermissionRead))
require.Nil(t, a.AllowAccess(Everyone, "__*_", PermissionRead))
require.Nil(t, a.Authorize(nil, "allowed_", PermissionRead))
require.Nil(t, a.Authorize(nil, "__allowed_", PermissionRead))
require.Nil(t, a.Authorize(nil, "_allowed_", PermissionRead)) // The "%" in "%\_" matches the first "_"
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "notallowed", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "_notallowed", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "__notallowed", PermissionRead))
}
func TestManager_Topic_Wildcard_With_Underscore(t *testing.T) {
f := filepath.Join(t.TempDir(), "user.db")
a := newTestManagerFromFile(t, f, "", PermissionDenyAll, DefaultUserPasswordBcryptCost, DefaultUserStatsQueueWriterInterval)
require.Nil(t, a.AllowAccess(Everyone, "mytopic_", PermissionReadWrite))
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead))
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionWrite))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite))
}
func TestToFromSQLWildcard(t *testing.T) {
require.Equal(t, "up%", toSQLWildcard("up*"))
require.Equal(t, "up\\_%", toSQLWildcard("up_*"))
require.Equal(t, "foo", toSQLWildcard("foo"))
require.Equal(t, "up*", fromSQLWildcard("up%"))
require.Equal(t, "up_*", fromSQLWildcard("up\\_%"))
require.Equal(t, "foo", fromSQLWildcard("foo"))
require.Equal(t, "up*", fromSQLWildcard(toSQLWildcard("up*")))
require.Equal(t, "up_*", fromSQLWildcard(toSQLWildcard("up_*")))
require.Equal(t, "foo", fromSQLWildcard(toSQLWildcard("foo")))
}
func TestMigrationFrom1(t *testing.T) {
2022-12-29 19:08:47 +01:00
filename := filepath.Join(t.TempDir(), "user.db")
db, err := sql.Open("sqlite3", filename)
require.Nil(t, err)
// Create "version 1" schema
_, err = db.Exec(`
BEGIN;
CREATE TABLE IF NOT EXISTS user (
user TEXT NOT NULL PRIMARY KEY,
pass TEXT NOT NULL,
role TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS access (
user TEXT NOT NULL,
topic TEXT NOT NULL,
read INT NOT NULL,
write INT NOT NULL,
PRIMARY KEY (topic, user)
);
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
version INT NOT NULL
);
INSERT INTO schemaVersion (id, version) VALUES (1, 1);
COMMIT;
`)
require.Nil(t, err)
// Insert a bunch of users and ACL entries
_, err = db.Exec(`
BEGIN;
INSERT INTO user (user, pass, role) VALUES ('ben', '$2a$10$EEp6gBheOsqEFsXlo523E.gBVoeg1ytphXiEvTPlNzkenBlHZBPQy', 'user');
INSERT INTO user (user, pass, role) VALUES ('phil', '$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C', 'admin');
INSERT INTO access (user, topic, read, write) VALUES ('ben', 'stats', 1, 1);
INSERT INTO access (user, topic, read, write) VALUES ('ben', 'secret', 1, 0);
INSERT INTO access (user, topic, read, write) VALUES ('*', 'stats', 1, 0);
COMMIT;
`)
require.Nil(t, err)
// Create manager to trigger migration
2023-01-28 15:03:14 +01:00
a := newTestManagerFromFile(t, filename, "", PermissionDenyAll, bcrypt.MinCost, DefaultUserStatsQueueWriterInterval)
2022-12-29 19:08:47 +01:00
checkSchemaVersion(t, a.db)
users, err := a.Users()
require.Nil(t, err)
require.Equal(t, 3, len(users))
phil, ben, everyone := users[0], users[1], users[2]
philGrants, err := a.Grants("phil")
require.Nil(t, err)
benGrants, err := a.Grants("ben")
require.Nil(t, err)
everyoneGrants, err := a.Grants(Everyone)
require.Nil(t, err)
require.True(t, strings.HasPrefix(phil.ID, "u_"))
2022-12-29 19:08:47 +01:00
require.Equal(t, "phil", phil.Name)
require.Equal(t, RoleAdmin, phil.Role)
2023-01-10 21:41:08 +01:00
require.Equal(t, syncTopicLength, len(phil.SyncTopic))
require.Equal(t, 0, len(philGrants))
2022-12-29 19:08:47 +01:00
require.True(t, strings.HasPrefix(ben.ID, "u_"))
require.NotEqual(t, phil.ID, ben.ID)
2022-12-29 19:08:47 +01:00
require.Equal(t, "ben", ben.Name)
require.Equal(t, RoleUser, ben.Role)
2023-01-10 21:41:08 +01:00
require.Equal(t, syncTopicLength, len(ben.SyncTopic))
require.NotEqual(t, ben.SyncTopic, phil.SyncTopic)
require.Equal(t, 2, len(benGrants))
require.Equal(t, "secret", benGrants[0].TopicPattern)
require.Equal(t, PermissionRead, benGrants[0].Allow)
require.Equal(t, "stats", benGrants[1].TopicPattern)
require.Equal(t, PermissionReadWrite, benGrants[1].Allow)
2022-12-29 19:08:47 +01:00
require.Equal(t, "u_everyone", everyone.ID)
2022-12-29 19:08:47 +01:00
require.Equal(t, Everyone, everyone.Name)
require.Equal(t, RoleAnonymous, everyone.Role)
require.Equal(t, 1, len(everyoneGrants))
require.Equal(t, "stats", everyoneGrants[0].TopicPattern)
require.Equal(t, PermissionRead, everyoneGrants[0].Allow)
2022-12-29 19:08:47 +01:00
}
2023-08-18 22:44:52 +02:00
func TestMigrationFrom4(t *testing.T) {
filename := filepath.Join(t.TempDir(), "user.db")
db, err := sql.Open("sqlite3", filename)
require.Nil(t, err)
// Create "version 4" schema
_, err = db.Exec(`
BEGIN;
CREATE TABLE IF NOT EXISTS tier (
id TEXT PRIMARY KEY,
code TEXT NOT NULL,
name TEXT NOT NULL,
messages_limit INT NOT NULL,
messages_expiry_duration INT NOT NULL,
emails_limit INT NOT NULL,
calls_limit INT NOT NULL,
reservations_limit INT NOT NULL,
attachment_file_size_limit INT NOT NULL,
attachment_total_size_limit INT NOT NULL,
attachment_expiry_duration INT NOT NULL,
attachment_bandwidth_limit INT NOT NULL,
stripe_monthly_price_id TEXT,
stripe_yearly_price_id TEXT
);
CREATE UNIQUE INDEX idx_tier_code ON tier (code);
CREATE UNIQUE INDEX idx_tier_stripe_monthly_price_id ON tier (stripe_monthly_price_id);
CREATE UNIQUE INDEX idx_tier_stripe_yearly_price_id ON tier (stripe_yearly_price_id);
CREATE TABLE IF NOT EXISTS user (
id TEXT PRIMARY KEY,
tier_id TEXT,
user TEXT NOT NULL,
pass TEXT NOT NULL,
role TEXT CHECK (role IN ('anonymous', 'admin', 'user')) NOT NULL,
prefs JSON NOT NULL DEFAULT '{}',
sync_topic TEXT NOT NULL,
stats_messages INT NOT NULL DEFAULT (0),
stats_emails INT NOT NULL DEFAULT (0),
stats_calls INT NOT NULL DEFAULT (0),
stripe_customer_id TEXT,
stripe_subscription_id TEXT,
stripe_subscription_status TEXT,
stripe_subscription_interval TEXT,
stripe_subscription_paid_until INT,
stripe_subscription_cancel_at INT,
created INT NOT NULL,
deleted INT,
FOREIGN KEY (tier_id) REFERENCES tier (id)
);
CREATE UNIQUE INDEX idx_user ON user (user);
CREATE UNIQUE INDEX idx_user_stripe_customer_id ON user (stripe_customer_id);
CREATE UNIQUE INDEX idx_user_stripe_subscription_id ON user (stripe_subscription_id);
CREATE TABLE IF NOT EXISTS user_access (
user_id TEXT NOT NULL,
topic TEXT NOT NULL,
read INT NOT NULL,
write INT NOT NULL,
owner_user_id INT,
PRIMARY KEY (user_id, topic),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE,
FOREIGN KEY (owner_user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS user_token (
user_id TEXT NOT NULL,
token TEXT NOT NULL,
label TEXT NOT NULL,
last_access INT NOT NULL,
last_origin TEXT NOT NULL,
expires INT NOT NULL,
PRIMARY KEY (user_id, token),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS user_phone (
user_id TEXT NOT NULL,
phone_number TEXT NOT NULL,
PRIMARY KEY (user_id, phone_number),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
version INT NOT NULL
);
INSERT INTO user (id, user, pass, role, sync_topic, created)
VALUES ('u_everyone', '*', '', 'anonymous', '', UNIXEPOCH())
ON CONFLICT (id) DO NOTHING;
INSERT INTO schemaVersion (id, version) VALUES (1, 4);
COMMIT;
`)
require.Nil(t, err)
// Insert a few ACL entries
_, err = db.Exec(`
BEGIN;
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'mytopic_', 1, 1);
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'up%', 1, 1);
INSERT INTO user_access (user_id, topic, read, write) values ('u_everyone', 'down_%', 1, 1);
COMMIT;
`)
require.Nil(t, err)
// Create manager to trigger migration
a := newTestManagerFromFile(t, filename, "", PermissionDenyAll, bcrypt.MinCost, DefaultUserStatsQueueWriterInterval)
checkSchemaVersion(t, a.db)
// Add another
require.Nil(t, a.AllowAccess(Everyone, "left_*", PermissionReadWrite))
// Check "external view" of grants
everyoneGrants, err := a.Grants(Everyone)
require.Nil(t, err)
require.Equal(t, 4, len(everyoneGrants))
require.Equal(t, "mytopic_", everyoneGrants[0].TopicPattern)
require.Equal(t, "down_*", everyoneGrants[1].TopicPattern)
require.Equal(t, "left_*", everyoneGrants[2].TopicPattern)
2023-08-18 22:44:52 +02:00
require.Equal(t, "up*", everyoneGrants[3].TopicPattern)
// Check they are stored correctly in the database
rows, err := db.Query(`SELECT topic FROM user_access WHERE user_id = 'u_everyone' ORDER BY topic`)
require.Nil(t, err)
topicPatterns := make([]string, 0)
for rows.Next() {
var topicPattern string
require.Nil(t, rows.Scan(&topicPattern))
topicPatterns = append(topicPatterns, topicPattern)
}
require.Nil(t, rows.Close())
require.Equal(t, 4, len(topicPatterns))
require.Equal(t, "down\\_%", topicPatterns[0])
require.Equal(t, "left\\_%", topicPatterns[1])
require.Equal(t, "mytopic\\_", topicPatterns[2])
require.Equal(t, "up%", topicPatterns[3])
// Check that ACL works as excepted
require.Nil(t, a.Authorize(nil, "down_123", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "downX123", PermissionRead))
require.Nil(t, a.Authorize(nil, "left_abc", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "leftX123", PermissionRead))
require.Nil(t, a.Authorize(nil, "mytopic_", PermissionRead))
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionRead))
require.Nil(t, a.Authorize(nil, "up123", PermissionRead))
require.Nil(t, a.Authorize(nil, "up", PermissionRead)) // % matches 0 or more characters
}
2022-12-29 19:08:47 +01:00
func checkSchemaVersion(t *testing.T, db *sql.DB) {
rows, err := db.Query(`SELECT version FROM schemaVersion`)
require.Nil(t, err)
require.True(t, rows.Next())
var schemaVersion int
require.Nil(t, rows.Scan(&schemaVersion))
require.Equal(t, currentSchemaVersion, schemaVersion)
require.Nil(t, rows.Close())
}
func newTestManager(t *testing.T, defaultAccess Permission) *Manager {
2023-01-28 15:03:14 +01:00
return newTestManagerFromFile(t, filepath.Join(t.TempDir(), "user.db"), "", defaultAccess, bcrypt.MinCost, DefaultUserStatsQueueWriterInterval)
2022-12-29 19:08:47 +01:00
}
2023-01-28 15:03:14 +01:00
func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defaultAccess Permission, bcryptCost int, statsWriterInterval time.Duration) *Manager {
a, err := NewManager(filename, startupQueries, defaultAccess, bcryptCost, statsWriterInterval)
2022-01-26 03:57:28 +01:00
require.Nil(t, err)
return a
}