Files
dolthub--doltgresql/testing/go/auth_test.go
T
2026-07-13 12:32:25 +08:00

1623 lines
62 KiB
Go

// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/server/functions"
)
const (
authTestSuperUser = "auth_test_super"
authTestSuperPass = "auth_test_spass"
authTestBasicUser = "auth_test_basic"
authTestBasicPass = "auth_test_bpass"
)
var (
authTestCreateSuperUser = fmt.Sprintf("create user if not exists '%s' with superuser password '%s';", authTestSuperUser, authTestSuperPass)
authTestCreateBasicUser = fmt.Sprintf("create user if not exists '%s' with password '%s'", authTestBasicUser, authTestBasicPass)
)
func TestAuthTests(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: `Simple CREATE USER and DROP USER`,
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `hello`,
ExpectedErr: `authentication failed`,
},
{
Query: `CREATE USER user1 PASSWORD 'hello';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 2;`,
Username: `user1`,
Password: `hello`,
Expected: []sql.Row{{2}},
},
{
Query: `DROP USER user1;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 3;`,
Username: `user1`,
Password: `hello`,
ExpectedErr: `authentication failed`,
},
},
},
{
Name: `ALTER PASSWORD`,
Assertions: []ScriptTestAssertion{
{
Query: `CREATE USER user1 PASSWORD 'something';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `something`,
Expected: []sql.Row{{1}},
},
{
Query: `ALTER USER user1 PASSWORD 'another_thing';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 2;`,
Username: `user1`,
Password: `something`,
ExpectedErr: `authentication failed`,
},
{
Query: `SELECT 3;`,
Username: `user1`,
Password: `another_thing`,
Expected: []sql.Row{{3}},
},
{ // No password will work, the user is effectively unable to be accessed with password-based auth
Query: `ALTER USER user1 WITH PASSWORD NULL;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 4;`,
Username: `user1`,
Password: `something`,
ExpectedErr: `authentication failed`,
},
{
Query: `SELECT 5;`,
Username: `user1`,
Password: `another_thing`,
ExpectedErr: `authentication failed`,
},
{
Query: `SELECT 6;`,
Username: `user1`,
Password: ``, // Even the empty password won't work
ExpectedErr: `authentication failed`,
},
{
Query: `ALTER USER user1 PASSWORD 'different484';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 7;`,
Username: `user1`,
Password: `different484`,
Expected: []sql.Row{{7}},
},
},
},
{
Name: `ALTER LOGIN`,
Assertions: []ScriptTestAssertion{
{ // By default, roles cannot be logged into
Query: `CREATE ROLE user1 PASSWORD 'pass1';`,
Expected: []sql.Row{},
},
{ // Users can be logged into by default, this is the only difference between roles and users
Query: `CREATE USER user2 PASSWORD 'pass2';`,
Expected: []sql.Row{},
},
{ // A role with LOGIN defined is exactly equivalent to a default user
Query: `CREATE ROLE user3 PASSWORD 'pass3' LOGIN;`,
Expected: []sql.Row{},
},
{ // A user with NOLOGIN defined is exactly equivalent to a default role
Query: `CREATE USER user4 PASSWORD 'pass4' NOLOGIN;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `pass1`,
ExpectedErr: `authentication failed`,
},
{
Query: `SELECT 2;`,
Username: `user2`,
Password: `pass2`,
Expected: []sql.Row{{2}},
},
{
Query: `SELECT 3;`,
Username: `user3`,
Password: `pass3`,
Expected: []sql.Row{{3}},
},
{
Query: `SELECT 4;`,
Username: `user4`,
Password: `pass4`,
ExpectedErr: `authentication failed`,
},
{ // We'll flip LOGIN/NOLOGIN statuses
Query: `ALTER USER user1 WITH LOGIN;`,
Expected: []sql.Row{},
},
{
Query: `ALTER USER user2 WITH NOLOGIN;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 5;`,
Username: `user1`,
Password: `pass1`,
Expected: []sql.Row{{5}},
},
{
Query: `SELECT 6;`,
Username: `user2`,
Password: `pass2`,
ExpectedErr: `authentication failed`,
},
},
},
{
Name: `CREATE USER IF NOT EXISTS`,
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `hello`,
ExpectedErr: `authentication failed`,
},
{
Query: `CREATE USER user1 PASSWORD 'hello1';`,
Expected: []sql.Row{},
},
{
Query: `CREATE USER user1 PASSWORD 'hello2';`,
ExpectedErr: `already exists`,
},
{
Query: `CREATE USER IF NOT EXISTS user1 PASSWORD 'hello3';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 2;`,
Username: `user1`,
Password: `hello1`,
Expected: []sql.Row{{2}},
},
{
Query: `CREATE ROLE IF NOT EXISTS user2 PASSWORD 'hi1' LOGIN;`,
Expected: []sql.Row{},
},
{
Query: `CREATE ROLE user2 PASSWORD 'hi2' LOGIN;`,
ExpectedErr: `already exists`,
},
{
Query: `CREATE ROLE IF NOT EXISTS user2 PASSWORD 'hi3' LOGIN;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 3;`,
Username: `user2`,
Password: `hi1`,
Expected: []sql.Row{{3}},
},
},
},
{
Name: `DROP USER IF EXISTS`,
Assertions: []ScriptTestAssertion{
{
Query: `CREATE USER user1 PASSWORD 'hello1';`,
Expected: []sql.Row{},
},
{
Query: `CREATE USER user2 PASSWORD 'hello2';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `hello1`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT 2;`,
Username: `user2`,
Password: `hello2`,
Expected: []sql.Row{{2}},
},
{
Query: `DROP USER user1;`,
Expected: []sql.Row{},
},
{
Query: `DROP USER user1;`,
ExpectedErr: `does not exist`,
},
{
Query: `DROP USER IF EXISTS user1;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 3;`,
Username: `user1`,
Password: `hello1`,
ExpectedErr: `authentication failed`,
},
{
Query: `DROP ROLE IF EXISTS user2;`,
Expected: []sql.Row{},
},
{
Query: `DROP ROLE user2;`,
ExpectedErr: `does not exist`,
},
{
Query: `DROP ROLE IF EXISTS user2;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 4;`,
Username: `user2`,
Password: `hello2`,
ExpectedErr: `authentication failed`,
},
},
},
{
Name: `DROP USER with multiple users`,
Assertions: []ScriptTestAssertion{
{
Query: `CREATE USER user1 PASSWORD 'hello1';`,
Expected: []sql.Row{},
},
{
Query: `CREATE USER user2 PASSWORD 'hello2';`,
Expected: []sql.Row{},
},
{
Query: `CREATE USER user3 PASSWORD 'hello3';`,
Expected: []sql.Row{},
},
{
Query: `SELECT 1;`,
Username: `user1`,
Password: `hello1`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT 2;`,
Username: `user2`,
Password: `hello2`,
Expected: []sql.Row{{2}},
},
{
Query: `SELECT 3;`,
Username: `user3`,
Password: `hello3`,
Expected: []sql.Row{{3}},
},
{
Query: `DROP USER user1, user3;`,
Expected: []sql.Row{},
},
{
Query: `SELECT 4;`,
Username: `user1`,
Password: `hello1`,
ExpectedErr: `authentication failed`,
},
{
Query: `SELECT 5;`,
Username: `user2`,
Password: `hello2`,
Expected: []sql.Row{{5}},
},
{
Query: `SELECT 6;`,
Username: `user3`,
Password: `hello3`,
ExpectedErr: `authentication failed`,
},
},
},
{
Name: `GRANT/REVOKE SELECT Privilege`,
SetUpScript: []string{
`CREATE USER user1 PASSWORD 'a';`,
`CREATE USER user2 PASSWORD 'b';`,
`GRANT ALL PRIVILEGES ON SCHEMA public TO user1;`,
`GRANT ALL PRIVILEGES ON SCHEMA public TO user2;`,
`GRANT ALL PRIVILEGES ON test TO user1 WITH GRANT OPTION;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE test (pk INT4 PRIMARY KEY);`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO test VALUES (1), (5), (6);`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}, {5}, {6}},
},
{
Query: `SELECT * FROM test ORDER BY pk`,
Username: `user2`,
Password: `b`,
ExpectedErr: `denied`,
},
{
Query: `GRANT SELECT ON test TO user2;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk`,
Username: `user2`,
Password: `b`,
Expected: []sql.Row{{1}, {5}, {6}},
},
{
Query: `REVOKE SELECT ON test FROM user2;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk`,
Username: `user2`,
Password: `b`,
ExpectedErr: `denied`,
},
{
Query: `GRANT SELECT ON test TO PUBLIC;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk`,
Username: `user2`,
Password: `b`,
Expected: []sql.Row{{1}, {5}, {6}},
},
},
},
{
Name: `INSERT, UPDATE, DELETE Privileges`,
SetUpScript: []string{
`CREATE TABLE test (pk INT4 PRIMARY KEY);`,
`INSERT INTO test VALUES (1), (6), (7);`,
`CREATE USER user1 PASSWORD 'a';`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test ORDER BY pk;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `WITH cte AS (SELECT * FROM test ORDER BY pk) SELECT * FROM cte;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `INSERT INTO test VALUES (10);`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `UPDATE test SET pk=pk+20;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `DELETE FROM test WHERE pk > 3;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT SELECT, INSERT, UPDATE, DELETE ON test TO user1;`,
Username: `postgres`,
Password: `password`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}, {6}, {7}},
},
{
Skip: true, // CTEs are seen as different tables
Query: `WITH cte AS (SELECT * FROM test ORDER BY pk) SELECT * FROM cte;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}, {6}, {7}},
},
{
Query: `INSERT INTO test VALUES (10);`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `UPDATE test SET pk=pk+20;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `DELETE FROM test WHERE pk = 21;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{26}, {27}, {30}},
},
{
Query: `REVOKE SELECT, INSERT, UPDATE, DELETE ON test FROM user1;`,
Username: `postgres`,
Password: `password`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test ORDER BY pk;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `INSERT INTO test VALUES (100);`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `UPDATE test SET pk=pk+200;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `DELETE FROM test WHERE pk > 3;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
},
},
{
Name: `CREATE privilege ON SEQUENCE and ROUTINE`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE USER user2 PASSWORD 'b';`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE FUNCTION testfunc3() RETURNS int AS $$ BEGIN RETURN 3; END; $$ LANGUAGE plpgsql",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `CREATE PROCEDURE interpreted_example_3(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('3' || input); END; $$ LANGUAGE plpgsql;`,
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: "CREATE SEQUENCE genre_id_seq_by_3 AS integer START WITH 1 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 1;",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT CREATE ON SCHEMA public TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "CREATE FUNCTION testfunc3() RETURNS int AS $$ BEGIN RETURN 3; END; $$ LANGUAGE plpgsql",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `CREATE PROCEDURE interpreted_example_3(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('3' || input); END; $$ LANGUAGE plpgsql;`,
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: "CREATE SEQUENCE genre_id_seq_by_3 AS integer START WITH 1 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 1;",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Skip: true, // user1 is owner of this sequence, so it should have all privileges to it.
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}},
},
{
Query: `REVOKE USAGE ON SEQUENCE genre_id_seq_by_3 FROM user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Skip: true, // user1 is owner of this sequence, so it should have all privileges to it.
// user1 is the owner of the sequence, so the revoke is ignored.
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{3}},
},
{
Skip: true, // not supported yet
Query: `ALTER SEQUENCE genre_id_seq_by_3 OWNER TO auth_test_user;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Skip: true, // depends on ALTER SEQUENCE ... OWNER TO
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
},
},
{
Skip: true,
Name: `owner of a relation has all privileges granted by default and cannot be revoked unless ownership is altered`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE USER user2 PASSWORD 'b';`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TABLE mytable (pk int);",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT CREATE ON SCHEMA public TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "CREATE TABLE mytable (pk int);",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: "SELECT * from mytable;",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
},
},
{
Name: `GRANT/REVOKE USAGE ON SCHEMA`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE SEQUENCE genre_id_seq_by_3 AS integer START WITH 1 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 1;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT USAGE ON SCHEMA public TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}},
},
{
Query: `REVOKE USAGE ON SCHEMA public FROM user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT nextval('genre_id_seq_by_3');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
},
},
{
Name: `privileges ON FUNCTION`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
"CREATE FUNCTION testfunc1() RETURNS int AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql",
"CREATE FUNCTION testfunc2() RETURNS int AS $$ BEGIN RETURN 2; END; $$ LANGUAGE plpgsql",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT testfunc1();",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT ALL ON FUNCTION public.testfunc1() TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT testfunc1();",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}},
},
{
Query: "SELECT testfunc2();",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `REVOKE ALL ON FUNCTION testfunc1() FROM user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT testfunc1();",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
// full privileges are granted for PUBLIC on system functions by default for now.
Query: "SELECT UPPER('hello');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{"HELLO"}},
},
{
// check dolt system functions work
Query: "SELECT DOLT_CHECKOUT('main');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{"{0,\"Already on branch 'main'\"}"}},
},
},
},
{
Name: `privileges ON PROCEDURE`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE TABLE test (v1 TEXT);`,
`CREATE PROCEDURE public.interpreted_example_1(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('1' || input); END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE interpreted_example_3(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('3' || input); END; $$ LANGUAGE plpgsql;`,
`GRANT ALL PRIVILEGES ON test TO user1 WITH GRANT OPTION;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL interpreted_example_1('12');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT ALL ON PROCEDURE public.interpreted_example_1(input TEXT) TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_example_1('22');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{
{"122"},
},
},
{
Query: "CALL interpreted_example_3('32');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: "SELECT * FROM test;",
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{
{"122"},
},
},
{
Query: `REVOKE ALL ON PROCEDURE public.interpreted_example_1(input TEXT) FROM user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_example_1('42');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: "SELECT * FROM test;",
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{
{"122"},
},
},
},
},
{
Name: `SELECT/USAGE privileges ON SEQUENCE`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE USER user2 PASSWORD 'b';`,
`CREATE SEQUENCE genre_id_seq_by_2 AS integer START WITH 1 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 1;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT nextval('genre_id_seq_by_2');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT USAGE ON SEQUENCE public.genre_id_seq_by_2 TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT nextval('genre_id_seq_by_2');",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{1}},
},
{
Query: `GRANT SELECT ON SEQUENCE public.genre_id_seq_by_2 TO user2;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "SELECT nextval('genre_id_seq_by_2');",
Username: `user2`,
Password: `b`,
ExpectedErr: `denied`,
},
{
Skip: true, // TODO: support sequence metadata flag 'is_called'
Query: "SELECT is_called FROM genre_id_seq_by_2;",
Username: `user2`,
Password: `b`,
Expected: []sql.Row{{true}},
},
},
},
{
Name: `user can create table with sequence that they don't have privileges for but they cannot use it'`,
SetUpScript: []string{
authTestCreateSuperUser,
`CREATE USER user1 PASSWORD 'a';`,
`CREATE SEQUENCE genre_id_seq_by_2 AS integer START WITH 1 INCREMENT BY 2 NO MINVALUE NO MAXVALUE CACHE 1;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT nextval('genre_id_seq_by_2');",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: `GRANT CREATE ON SCHEMA public TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "create table test_by_user1 (pk int, v1 INTEGER DEFAULT nextval('genre_id_seq_by_2'));",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
// TODO: remove this once we support ownership privileges.
// test_by_user1 table is owned by user1, so it should have all privileges
Query: `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "insert into test_by_user1(pk) values (3);",
Username: `user1`,
Password: `a`,
ExpectedErr: `denied`,
},
{
Query: "SELECT * FROM test_by_user1;",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: `GRANT USAGE ON SEQUENCE public.genre_id_seq_by_2 TO user1;`,
Username: authTestSuperUser,
Password: authTestSuperPass,
Expected: []sql.Row{},
},
{
Query: "insert into test_by_user1(pk) values (3);",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test_by_user1;",
Username: `user1`,
Password: `a`,
Expected: []sql.Row{{3, 1}},
},
},
},
{
Name: "Branch Control: Namespace entries block",
SetUpScript: []string{
"DELETE FROM dolt_branch_control WHERE user = '%';",
"INSERT INTO dolt_branch_control VALUES ('%', '%', 'postgres', '%', 'admin');",
`CREATE USER testuser PASSWORD 'a';`,
"GRANT ALL PRIVILEGES ON SCHEMA public TO testuser;",
},
Assertions: []ScriptTestAssertion{
{ // Empty table, all branches are allowed
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('otherbranch1');",
Expected: []sql.Row{{"{0}"}},
},
{ // Prefix "other" is now locked by postgres
Username: "postgres",
Password: "password",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'other%', 'postgres', '%');",
Expected: []sql.Row{},
},
{
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('otherbranch2');",
ExpectedErr: "cannot create a branch named",
},
{ // Allow testuser to use the "other" prefix
Username: "postgres",
Password: "password",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'other%', 'testuser', '%');",
Expected: []sql.Row{},
},
{
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('otherbranch2');",
Expected: []sql.Row{{"{0}"}},
},
{ // Create a longer match, which takes precedence over shorter matches
Username: "postgres",
Password: "password",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'otherbranch%', 'postgres', '%');",
Expected: []sql.Row{},
},
{ // Matches both "other%" and "otherbranch%", but "otherbranch%" wins by being the longer match
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('otherbranch3');",
ExpectedErr: "cannot create a branch named",
},
{ // This doesn't match the longer rule, so testuser has access the namespace
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('other3');",
Expected: []sql.Row{{"{0}"}},
},
{
Username: "postgres",
Password: "password",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'otherbranch%', 'testuser', '%');",
Expected: []sql.Row{},
},
{
Username: "testuser",
Password: "a",
Query: "SELECT DOLT_BRANCH('otherbranch3');",
Expected: []sql.Row{{"{0}"}},
},
},
},
{
Name: "Require admin to modify tables",
SetUpScript: []string{
`DELETE FROM dolt_branch_control WHERE "user" = '%';`,
"INSERT INTO dolt_branch_control VALUES ('%', '%', 'postgres', '%', 'admin');",
`CREATE USER a PASSWORD 'a';`,
`CREATE USER b PASSWORD 'b';`,
"GRANT ALL PRIVILEGES ON SCHEMA public TO a;",
"GRANT ALL PRIVILEGES ON SCHEMA public TO b;",
"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO a;",
"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO b;",
"INSERT INTO dolt_branch_control VALUES ('%', 'other', 'a', '%', 'write'), ('%', 'prefix%', 'a', '%', 'admin')",
},
Assertions: []ScriptTestAssertion{
{
Username: "a",
Password: "a",
Query: `DELETE FROM dolt_branch_control WHERE "branch" = 'other';`,
ExpectedErr: `cannot delete the row`,
},
{
Username: "b",
Password: "b",
Query: `DELETE FROM dolt_branch_control WHERE "branch" = 'other';`,
ExpectedErr: `cannot delete the row`,
},
{
Username: "b",
Password: "b",
Query: `DELETE FROM dolt_branch_control WHERE "branch" = 'prefix%';`,
ExpectedErr: `cannot delete the row`,
},
{
Username: "a",
Password: "a",
Query: "INSERT INTO dolt_branch_control VALUES ('%', 'prefix1%', 'b', '%', 'write');",
Expected: []sql.Row{},
},
{
Username: "b",
Password: "b",
Query: `DELETE FROM dolt_branch_control WHERE "branch" = 'prefix1%';`,
ExpectedErr: `cannot delete the row`,
},
{ // Must have permission on the new name as well
Username: "a",
Password: "a",
Query: `UPDATE dolt_branch_control SET "branch" = 'other1' WHERE "user" = 'b' AND "branch" = 'prefix1%';`,
ExpectedErr: `cannot update the row`,
},
{
Username: "b",
Password: "b",
Query: `UPDATE dolt_branch_control SET "permissions" = 'admin' WHERE "user" = 'b' AND "branch" = 'prefix1%';`,
ExpectedErr: `cannot update the row`,
},
{
Username: "a",
Password: "a",
Query: `UPDATE dolt_branch_control SET "permissions" = 'admin' WHERE "user" = 'b' AND "branch" = 'prefix1%';`,
Expected: []sql.Row{},
},
{
Username: "b",
Password: "b",
Query: `DELETE FROM dolt_branch_control WHERE "branch" = 'prefix1%';`,
Expected: []sql.Row{},
},
{
Username: "b",
Password: "b",
Query: "INSERT INTO dolt_branch_control VALUES ('%', 'prefix1%', 'b', '%', 'admin');",
ExpectedErr: `cannot add the row`,
},
{ // Since "a" has admin on "prefix%", they can also insert into the namespace table
Username: "a",
Password: "a",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'prefix___', 'a', '%');",
Expected: []sql.Row{},
},
{
Username: "b",
Password: "b",
Query: "INSERT INTO dolt_branch_namespace_control VALUES ('%', 'prefix', 'b', '%');",
ExpectedErr: `cannot add the row`,
},
{
Username: "a",
Password: "a",
Query: `UPDATE dolt_branch_namespace_control SET "branch" = 'prefix%';`,
Expected: []sql.Row{},
},
{
Username: "a",
Password: "a",
Query: `UPDATE dolt_branch_namespace_control SET "branch" = 'other';`,
ExpectedErr: `cannot update the row`,
},
{
Username: "b",
Password: "b",
Query: `DELETE FROM dolt_branch_namespace_control WHERE "branch" = 'prefix%';`,
ExpectedErr: `cannot delete the row`,
},
{
Username: "b",
Password: "b",
Query: `UPDATE dolt_branch_namespace_control SET "branch" = 'anything';`,
ExpectedErr: `cannot update the row`,
},
},
},
})
}
// TestAuthDoltProcedures tests that Dolt procedure functions apply permission checks for SUPERUSERs and basic users in
// SELECT statements. We test both CALL and SELECT to avoid regressions in [node.Call], where previous Doltgres'
// versions fell back to the node runner (on CALL an error is returned to use SELECT now). Some procedures also use [os]
// package calls like [os.TempDir] which can crash [filesys.InMemFS], so we use the local file system.
//
// Each time a new Dolt procedure is introduced in a ScriptTest, it's grouped into a set of related procedures. Each set
// is separated by a new line.
func TestAuthDoltProcedures(t *testing.T) {
tempDir, err := os.MkdirTemp("", t.Name())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll(tempDir) })
authTestFireUrl := func(path string) string {
return "file://" + filepath.ToSlash(filepath.Join(tempDir, path))
}
RunScripts(t, []ScriptTest{
{
UseLocalFileSystem: true,
Name: "SUPERUSER authorization for CALL executing Dolt stored procedures",
SetUpScript: []string{
authTestCreateSuperUser,
"create table test_table (v int);",
"insert into test_table values (1);",
"select dolt_add('test_table');",
"select dolt_commit('-m', 'add test table');",
},
Assertions: []ScriptTestAssertion{
authTestAssertAsSuper(fmt.Sprintf("call dolt_backup('sync-url', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper(fmt.Sprintf("call dolt_backup('add', 'bak1', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_checkout('-b', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_branch('new_branch');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("insert into test_table values (2);", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_add('.');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_commit('-m', 'amend test table');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_checkout('main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_cherry_pick('test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_clean('--dry-run');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper(fmt.Sprintf("call dolt_clone('%s', 'cloned_bak1');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("set authtest.hash = ''", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_commit_hash_out('authtest.hash', '-am', 'add val 3 to test table')", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_checkout('-b', 'conflict');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("update test_table set v = -1 where v = 1;", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_commit('-am', 'amend 1 to -1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_checkout('main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("update test_table set v = -2 where v = 1;", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_commit('-am', 'amend 2 to -2');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("set dolt_allow_commit_conflicts to 1;", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_merge('conflict');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_conflicts_resolve('--theirs', 'test_table');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_count_commits('--from=main', '--to=test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_backup('remove', 'bak1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper(fmt.Sprintf("call dolt_remote('add', 'origin', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_fetch('origin', 'main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_undrop('cloned_bak1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_commit('-am', 'resolve conflicts');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_update_column_tag('test_table', 'v', '123');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAsSuper("call dolt_purge_dropped_databases();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_checkout('test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_rebase('-i', 'main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_rebase('--abort');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("create table to_rm (v int);", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_add('to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_commit('-m', 'clean state to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_rm('to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_gc('--shallow');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAsSuper("call dolt_thread_dump();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_commit('-m', 'rm to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_push('origin', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_pull('origin', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_reset('--soft', 'HEAD~1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_stash('push', 'to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_tag('-m', 'dolt_rm procedure', 'to_rm', 'HEAD');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_verify_constraints('--all');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsSuper("call dolt_stats_info('--short');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAsSuper("call dolt_stats_wait();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAsSuper("call dolt_stats_flush();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAsSuper("call dolt_stats_gc();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAsSuper("call dolt_stats_purge();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAsSuper("call dolt_stats_restart();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAsSuper("call dolt_stats_once();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
},
},
{
UseLocalFileSystem: true,
Name: "Basic user authentication for CALL executing Dolt stored procedures",
SetUpScript: []string{
authTestCreateBasicUser,
fmt.Sprintf("alter user %s createdb;", authTestBasicUser),
authTestCreateSuperUser,
"create table test_table (v int);",
"insert into test_table values (1);",
"select dolt_add('test_table');",
"select dolt_commit('-m', 'add test table');",
},
Assertions: []ScriptTestAssertion{
authTestAssertAsBasic(fmt.Sprintf("call dolt_backup('sync-url', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic(fmt.Sprintf("call dolt_backup('add', 'bak1', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
// Grant user access to test_table before checkout to avoid merge conflict in later cherry-pick.
authTestGrantBasic("schema public", "all"),
authTestGrantBasic("test_table", "select", "insert", "delete", "update"),
authTestAssertAsBasic("call dolt_checkout('-b', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_branch('new_branch');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("insert into test_table values (2);", []sql.Row{}, ""),
authTestAssertAsBasic("call dolt_add('.');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_commit('-m', 'amend test table');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_checkout('main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_cherry_pick('test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_clean('--dry-run');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic(fmt.Sprintf("call dolt_clone('%s', 'cloned_bak1');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("create database cloned_bak1;", []sql.Row{}, ""),
authTestAssertAsSuper("set authtest.hash = '';", []sql.Row{}, ""),
authTestAssertAsSuper("call dolt_commit_hash_out('authtest.hash', '-am', 'add val 3 to test table');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_checkout('-b', 'conflict');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("update test_table set v = -1 where v = 1;", []sql.Row{}, ""),
authTestAssertAsBasic("call dolt_commit('-am', 'amend 1 to -1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_checkout('main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("update test_table set v = -2 where v = 1;", []sql.Row{}, ""),
authTestAssertAsBasic("call dolt_commit('-am', 'amend 2 to -2');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("set dolt_allow_commit_conflicts to 1;", []sql.Row{}, ""),
authTestAssertAsBasic("call dolt_merge('conflict');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_conflicts_resolve('--theirs', 'test_table');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_count_commits('--from=main', '--to=test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_backup('remove', 'bak1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic(fmt.Sprintf("call dolt_remote('add', 'origin', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_fetch('origin', 'main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_undrop('cloned_bak1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_commit('-am', 'resolve conflicts');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_update_column_tag('test_table', 'v', '123');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("drop database cloned_bak1;", []sql.Row{}, ""),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAssertAsBasic("call dolt_purge_dropped_databases();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_checkout('test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_rebase('-i', 'main');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_rebase('--abort');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("create table to_rm (v int);", []sql.Row{}, ""),
authTestAssertAsBasic("call dolt_add('to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_commit('-m', 'clean state to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_rm('to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_gc('--shallow');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAssertAsBasic("call dolt_thread_dump();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_commit('-m', 'rm to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_push('origin', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_pull('origin', 'test');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_reset('--soft', 'HEAD~1');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_stash('push', 'to_rm');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_tag('-m', 'dolt_rm procedure', 'to_rm', 'HEAD');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_verify_constraints('--all');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestAssertAsBasic("call dolt_stats_info('--short');", nil, functions.ErrDoltProcedureSelectOnly.Error()),
// TODO(elianddb): "procedure aggregation is not yet supported" error blocks no-parameter CALLs
authTestSkipAssertAsBasic("call dolt_stats_wait();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAssertAsBasic("call dolt_stats_flush();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAssertAsBasic("call dolt_stats_gc();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAssertAsBasic("call dolt_stats_purge();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAssertAsBasic("call dolt_stats_restart();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
authTestSkipAssertAsBasic("call dolt_stats_once();", nil, functions.ErrDoltProcedureSelectOnly.Error()),
},
},
{
UseLocalFileSystem: true,
Name: "SUPERUSER authorization for SELECT executing Dolt stored procedures",
SetUpScript: []string{
authTestCreateSuperUser,
"create table test_table (v int);",
"insert into test_table values (1);",
"select dolt_add('test_table');",
"select dolt_commit('-m', 'add test table');",
},
Assertions: []ScriptTestAssertion{
authTestAssertAsSuper(fmt.Sprintf("select dolt_backup('sync-url', '%s');", authTestFireUrl("bak1")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper(fmt.Sprintf("select dolt_backup('add', 'bak1', '%s');", authTestFireUrl("bak1")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_checkout('-b', 'test');", []sql.Row{{"{0,\"Switched to branch 'test'\"}"}}, ""),
authTestAssertAsSuper("select dolt_branch('new_branch');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("insert into test_table values (2);", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_add('.');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select length(dolt_commit('-m', 'amend test table')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper("select dolt_checkout('main');", []sql.Row{{"{0,\"Switched to branch 'main'\"}"}}, ""),
authTestAssertAsSuper("select length(dolt_cherry_pick('test')::text);", []sql.Row{{40}}, ""),
authTestAssertAsSuper("select dolt_clean('--dry-run');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper(fmt.Sprintf("select dolt_clone('%s', 'cloned_bak1');", authTestFireUrl("bak1")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("set authtest.hash = '';", []sql.Row{}, ""),
// TODO(elianddb): variadic parameter support for Dolt stored procedures functions
authTestSkipAsSuper("select dolt_commit_hash_out('authtest.hash', '-am', 'add val 3 to test table');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_checkout('-b', 'conflict');", []sql.Row{{"{0,\"Switched to branch 'conflict'\"}"}}, ""),
authTestAssertAsSuper("update test_table set v = -1 where v = 1;", []sql.Row{}, ""),
authTestAssertAsSuper("select length(dolt_commit('-am', 'amend 1 to -1')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper("select dolt_checkout('main');", []sql.Row{{"{0,\"Switched to branch 'main'\"}"}}, ""),
authTestAssertAsSuper("update test_table set v = -2 where v = 1;", []sql.Row{}, ""),
authTestAssertAsSuper("select length(dolt_commit('-am', 'amend 2 to -2')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper("set dolt_allow_commit_conflicts to 1;", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_merge('conflict');", []sql.Row{{`{"",0,1,"conflicts found"}`}}, ""),
authTestAssertAsSuper("select dolt_conflicts_resolve('--theirs', 'test_table');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): unsupported type uint64
authTestSkipAsSuper("select dolt_count_commits('--from=main', '--to=test');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_backup('remove', 'bak1');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper(fmt.Sprintf("select dolt_backup('add', 'bak2', '%s');", authTestFireUrl("bak2")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_backup('sync', 'bak2');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper(fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_db');", authTestFireUrl("bak2")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("drop database restored_db;", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_backup('remove', 'bak2');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper(fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", authTestFireUrl("bak1")), []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_fetch('origin', 'main');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("drop database cloned_bak1", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_undrop('cloned_bak1');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select length(dolt_commit('-am', 'resolve conflicts')::text) = 34;", []sql.Row{{"t"}}, ""),
// TODO(elianddb): table test_table does not exist (also tried with public.test_table)
authTestSkipAsSuper("select dolt_update_column_tag('test_table', 'v', '123');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("drop database cloned_bak1", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_purge_dropped_databases();", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_checkout('test');", []sql.Row{{"{0,\"Switched to branch 'test'\"}"}}, ""),
authTestAssertAsSuper(
"select dolt_rebase('-i', 'main');",
[]sql.Row{{"{0,\"interactive rebase started on branch dolt_rebase_test; adjust the rebase plan in the dolt_rebase table, then continue rebasing by calling dolt_rebase('--continue')\"}"}},
""),
authTestAssertAsSuper("select dolt_rebase('--abort');", []sql.Row{{"{0,\"Interactive rebase aborted\"}"}}, ""),
authTestAssertAsSuper("create table to_rm (v int);", []sql.Row{}, ""),
authTestAssertAsSuper("select dolt_add('to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select length(dolt_commit('-m', 'clean state to_rm')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper("select dolt_rm('to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_gc('--shallow');", []sql.Row{{"{0}"}}, ""),
// The paths for files, memory addresses, and number of goroutines can be different per OS.
authTestAssertAsSuper("select instr(dolt_thread_dump()::text, 'goroutine') > 0;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper("select length(dolt_commit('-m', 'rm to_rm')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsSuper(
"select dolt_push('origin', 'test');",
[]sql.Row{{fmt.Sprintf("{0,\"To %s\n * [new branch] test -> test\"}", authTestFireUrl("bak1"))}},
""),
authTestAssertAsSuper("select dolt_pull('origin', 'test');", []sql.Row{{"{0,0,\"Everything up-to-date\"}"}}, ""),
authTestAssertAsSuper("select dolt_reset('--soft', 'HEAD~1');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): unsupported type int
authTestSkipAsSuper("select dolt_stash('push', 'to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_tag('-m', 'dolt_rm procedure', 'to_rm', 'HEAD');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsSuper("select dolt_verify_constraints('--all');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): provider does not implement ExtendedStatsProvider
authTestSkipAsSuper("select dolt_stats_info('--short');", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_wait();", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_flush();", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_gc();", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_purge();", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_restart();", []sql.Row{{"{0}"}}, ""),
authTestSkipAsSuper("select dolt_stats_once();", []sql.Row{{"{0}"}}, ""),
},
},
{
UseLocalFileSystem: true,
Name: "Basic user authorization for SELECT executing Dolt stored procedures",
SetUpScript: []string{
authTestCreateBasicUser,
fmt.Sprintf("alter user %s createdb;", authTestBasicUser),
authTestCreateSuperUser,
"create table test_table (v int);",
"insert into test_table values (1);",
"select dolt_add('test_table');",
"select dolt_commit('-m', 'add test table');",
},
Assertions: []ScriptTestAssertion{
authTestAssertAsBasic(fmt.Sprintf("select dolt_backup('sync-url', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic(fmt.Sprintf("select dolt_backup('add', 'bak1', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedurePermissionDenied.Error()),
// Grant user access to test_table before checkout to avoid merge conflict in later cherry-pick.
authTestGrantBasic("schema public", "all"),
authTestGrantBasic("test_table", "select", "insert", "delete", "update"),
authTestAssertAsBasic("select dolt_checkout('-b', 'test');", []sql.Row{{"{0,\"Switched to branch 'test'\"}"}}, ""),
authTestAssertAsBasic("select dolt_branch('new_branch');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("insert into test_table values (2);", []sql.Row{}, ""),
authTestAssertAsBasic("select dolt_add('.');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select length(dolt_commit('-m', 'amend test table')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsBasic("select dolt_checkout('main');", []sql.Row{{"{0,\"Switched to branch 'main'\"}"}}, ""),
authTestAssertAsBasic("select length(dolt_cherry_pick('test')::text);", []sql.Row{{40}}, ""),
authTestAssertAsBasic("select dolt_clean('--dry-run');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic(fmt.Sprintf("select dolt_clone('%s', 'cloned_bak1');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("create database cloned_bak1;", []sql.Row{}, ""),
authTestAssertAsBasic("set authtest.hash = '';", []sql.Row{}, ""),
// TODO(elianddb): variadic parameter support for Dolt stored procedures
authTestSkipAssertAsBasic("select dolt_commit_hash_out('authtest.hash', '-am', 'add val 3 to test table');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_checkout('-b', 'conflict');", []sql.Row{{"{0,\"Switched to branch 'conflict'\"}"}}, ""),
authTestAssertAsBasic("update test_table set v = -1 where v = 1;", []sql.Row{}, ""),
authTestAssertAsBasic("select length(dolt_commit('-am', 'amend 1 to -1')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsBasic("select dolt_checkout('main');", []sql.Row{{"{0,\"Switched to branch 'main'\"}"}}, ""),
authTestAssertAsBasic("update test_table set v = -2 where v = 1;", []sql.Row{}, ""),
authTestAssertAsBasic("select length(dolt_commit('-am', 'amend 2 to -2')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsBasic("set dolt_allow_commit_conflicts to 1;", []sql.Row{}, ""),
authTestAssertAsBasic("select dolt_merge('conflict');", []sql.Row{{`{"",0,1,"conflicts found"}`}}, ""),
authTestAssertAsBasic("select dolt_conflicts_resolve('--theirs', 'test_table');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): unsupported type uint64
authTestSkipAssertAsBasic("select dolt_count_commits('--from=main', '--to=test');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_backup('remove', 'bak1');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_backup('sync', 'bak1');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic(fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_db');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic(fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", authTestFireUrl("bak1")), nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_fetch('origin', 'main');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("drop database cloned_bak1", []sql.Row{}, ""),
authTestAssertAsBasic("select dolt_undrop('cloned_bak1');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select length(dolt_commit('-am', 'resolve conflicts')::text) = 34;", []sql.Row{{"t"}}, ""),
// TODO(elianddb): table test_table does not exist (also tried with public.test_table)
authTestSkipAssertAsBasic("select dolt_update_column_tag('test_table', 'v', '123');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_purge_dropped_databases();", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_checkout('test');", []sql.Row{{"{0,\"Switched to branch 'test'\"}"}}, ""),
authTestAssertAsBasic(
"select dolt_rebase('-i', 'main');",
[]sql.Row{{"{0,\"interactive rebase started on branch dolt_rebase_test; adjust the rebase plan in the dolt_rebase table, then continue rebasing by calling dolt_rebase('--continue')\"}"}},
""),
authTestAssertAsBasic("select dolt_rebase('--abort');", []sql.Row{{"{0,\"Interactive rebase aborted\"}"}}, ""),
authTestAssertAsBasic("create table to_rm (v int);", []sql.Row{}, ""),
authTestAssertAsBasic("select dolt_add('to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select length(dolt_commit('-m', 'clean state to_rm')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsBasic("select dolt_rm('to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_gc('--shallow');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_thread_dump();", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select length(dolt_commit('-m', 'rm to_rm')::text) = 34;", []sql.Row{{"t"}}, ""),
authTestAssertAsBasic("select dolt_push('origin', 'test');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_pull('origin', 'test');", nil, functions.ErrDoltProcedurePermissionDenied.Error()),
authTestAssertAsBasic("select dolt_reset('--soft', 'HEAD~1');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): unsupported type int
authTestSkipAssertAsBasic("select dolt_stash('push', 'to_rm');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_tag('-m', 'dolt_rm procedure', 'to_rm', 'HEAD');", []sql.Row{{"{0}"}}, ""),
authTestAssertAsBasic("select dolt_verify_constraints('--all');", []sql.Row{{"{0}"}}, ""),
// TODO(elianddb): provider does not implement ExtendedStatsProvider
authTestSkipAssertAsBasic("select dolt_stats_info('--short');", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_wait();", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_flush();", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_gc();", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_purge();", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_restart();", []sql.Row{{"{0}"}}, ""),
authTestSkipAssertAsBasic("select dolt_stats_once();", []sql.Row{{"{0}"}}, ""),
},
},
})
}
// authTestAssertAsSuper returns a ScriptTestAssertion for the given |query|, |expectedResultSet|, and/or |expectedErr| using
// authTestSuperUser.
func authTestAssertAsSuper(query string, expectedResultSet []sql.Row, expectedErr string) ScriptTestAssertion {
return ScriptTestAssertion{
Username: authTestSuperUser,
Password: authTestSuperPass,
Query: query,
Expected: expectedResultSet,
ExpectedErr: expectedErr,
}
}
// authTestSkipAsSuper skips the returned assertion from authTestAssertAsSuper.
func authTestSkipAsSuper(query string, expectedResultSet []sql.Row, expectedErr string) ScriptTestAssertion {
assertion := authTestAssertAsSuper(query, expectedResultSet, expectedErr)
assertion.Skip = true
return assertion
}
// authTestAssertAsBasic returns a ScriptTestAssertion for the given |query|, |expectedResultSet|, and/or |expectedErr| using
// authTestBasicUser.
func authTestAssertAsBasic(query string, expected []sql.Row, expectedErr string) ScriptTestAssertion {
return ScriptTestAssertion{
Username: authTestBasicUser,
Password: authTestBasicPass,
Query: query,
Expected: expected,
ExpectedErr: expectedErr,
}
}
// authTestSkipAssertAsBasic skips the returned assertion from authTestAssertAsBasic.
func authTestSkipAssertAsBasic(query string, expected []sql.Row, expectedErr string) ScriptTestAssertion {
assertion := authTestAssertAsBasic(query, expected, expectedErr)
assertion.Skip = true
return assertion
}
// authTestGrantBasic grants |privileges| to authTestBasicUser on given |object| (include the object type in |object| if
// applicable).
func authTestGrantBasic(object string, privileges ...string) ScriptTestAssertion {
return ScriptTestAssertion{
Username: "postgres",
Password: "password",
Query: fmt.Sprintf("GRANT %s ON %s TO %s", strings.Join(privileges, ","), object, authTestBasicUser),
Expected: []sql.Row{},
}
}