|
@@ -330,7 +330,7 @@ func TestManager_Reservations(t *testing.T) {
|
|
|
a := newTestManager(t, PermissionDenyAll)
|
|
a := newTestManager(t, PermissionDenyAll)
|
|
|
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
|
|
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
|
|
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
|
|
- require.Nil(t, a.AddReservation("ben", "ztopic", PermissionDenyAll))
|
|
|
|
|
|
|
+ require.Nil(t, a.AddReservation("ben", "ztopic_", PermissionDenyAll))
|
|
|
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
|
|
require.Nil(t, a.AddReservation("ben", "readme", PermissionRead))
|
|
|
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
|
|
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
|
|
|
|
|
|
|
@@ -343,7 +343,7 @@ func TestManager_Reservations(t *testing.T) {
|
|
|
Everyone: PermissionRead,
|
|
Everyone: PermissionRead,
|
|
|
}, reservations[0])
|
|
}, reservations[0])
|
|
|
require.Equal(t, Reservation{
|
|
require.Equal(t, Reservation{
|
|
|
- Topic: "ztopic",
|
|
|
|
|
|
|
+ Topic: "ztopic_",
|
|
|
Owner: PermissionReadWrite,
|
|
Owner: PermissionReadWrite,
|
|
|
Everyone: PermissionDenyAll,
|
|
Everyone: PermissionDenyAll,
|
|
|
}, reservations[1])
|
|
}, reservations[1])
|
|
@@ -352,6 +352,14 @@ func TestManager_Reservations(t *testing.T) {
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|
|
|
require.True(t, b)
|
|
require.True(t, b)
|
|
|
|
|
|
|
|
|
|
+ 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)
|
|
|
|
|
+
|
|
|
b, err = a.HasReservation("notben", "readme")
|
|
b, err = a.HasReservation("notben", "readme")
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|
|
|
require.False(t, b)
|
|
require.False(t, b)
|
|
@@ -371,11 +379,17 @@ func TestManager_Reservations(t *testing.T) {
|
|
|
err = a.AllowReservation("phil", "readme")
|
|
err = a.AllowReservation("phil", "readme")
|
|
|
require.Equal(t, errTopicOwnedByOthers, err)
|
|
require.Equal(t, errTopicOwnedByOthers, err)
|
|
|
|
|
|
|
|
|
|
+ err = a.AllowReservation("phil", "ztopic_")
|
|
|
|
|
+ require.Equal(t, errTopicOwnedByOthers, err)
|
|
|
|
|
+
|
|
|
|
|
+ err = a.AllowReservation("phil", "ztopicX")
|
|
|
|
|
+ require.Nil(t, err)
|
|
|
|
|
+
|
|
|
err = a.AllowReservation("phil", "not-reserved")
|
|
err = a.AllowReservation("phil", "not-reserved")
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|
|
|
|
|
|
|
|
// Now remove them again
|
|
// Now remove them again
|
|
|
- require.Nil(t, a.RemoveReservations("ben", "ztopic", "readme"))
|
|
|
|
|
|
|
+ require.Nil(t, a.RemoveReservations("ben", "ztopic_", "readme"))
|
|
|
|
|
|
|
|
count, err = a.ReservationsCount("ben")
|
|
count, err = a.ReservationsCount("ben")
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|
|
@@ -978,7 +992,44 @@ func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) {
|
|
|
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
|
|
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func TestSqliteCache_Migration_From1(t *testing.T) {
|
|
|
|
|
|
|
+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) {
|
|
|
filename := filepath.Join(t.TempDir(), "user.db")
|
|
filename := filepath.Join(t.TempDir(), "user.db")
|
|
|
db, err := sql.Open("sqlite3", filename)
|
|
db, err := sql.Open("sqlite3", filename)
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|
|
@@ -1063,6 +1114,152 @@ func TestSqliteCache_Migration_From1(t *testing.T) {
|
|
|
require.Equal(t, PermissionRead, everyoneGrants[0].Allow)
|
|
require.Equal(t, PermissionRead, everyoneGrants[0].Allow)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+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, "down_*", everyoneGrants[0].TopicPattern)
|
|
|
|
|
+ require.Equal(t, "left_*", everyoneGrants[1].TopicPattern)
|
|
|
|
|
+ require.Equal(t, "mytopic_", everyoneGrants[2].TopicPattern)
|
|
|
|
|
+ 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
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func checkSchemaVersion(t *testing.T, db *sql.DB) {
|
|
func checkSchemaVersion(t *testing.T, db *sql.DB) {
|
|
|
rows, err := db.Query(`SELECT version FROM schemaVersion`)
|
|
rows, err := db.Query(`SELECT version FROM schemaVersion`)
|
|
|
require.Nil(t, err)
|
|
require.Nil(t, err)
|