Files
dbx/apps/desktop/src/lib/__tests__/database/databaseUserAdmin.spec.ts
T

224 lines
11 KiB
TypeScript
Vendored

import { describe, expect, it } from "vitest";
import {
dorisGrantPrivilegesSql,
dorisGrantsResult,
dorisPrivilegeTargetSql,
dorisUsersResult,
gaussdbMUserAdminProvider,
getDatabaseUserAdminProvider,
kingbaseShowGrantsSql,
kingbaseUserAdminProvider,
mysqlPrivilegeSelectionFromGrants,
nativeMysqlUserAdminProvider,
postgresUserAdminProvider,
resolveDatabaseUserAdminProviderForConnection,
} from "@/lib/database/databaseUserAdmin";
import type { ConnectionConfig, QueryResult } from "@/types/database";
function result(columns: string[], rows: QueryResult["rows"]): QueryResult {
return { columns, rows, affected_rows: 0, execution_time_ms: 1 };
}
function connection(dbType: ConnectionConfig["db_type"], driverProfile?: string): ConnectionConfig {
return {
id: `${dbType}-connection`,
name: dbType,
db_type: dbType,
driver_profile: driverProfile,
host: "localhost",
port: 5432,
username: "tester",
password: "",
};
}
describe("MySQL grant privilege selection", () => {
const availablePrivileges = ["SELECT", "INSERT", "UPDATE", "EXECUTE"];
it("expands ALL PRIVILEGES and reads WITH GRANT OPTION for the current scope", () => {
expect(
mysqlPrivilegeSelectionFromGrants({
grants: ["grant all privileges on `*`.* to 'root'@'%' WITH GRANT OPTION"],
database: "*",
table: "*",
availablePrivileges,
}),
).toEqual({ privileges: availablePrivileges, grantOption: true });
});
it("merges matching grants case-insensitively", () => {
expect(
mysqlPrivilegeSelectionFromGrants({
grants: ["GRANT SELECT, insert ON `Sales`.`Orders` TO 'app'@'%'", "GRANT UPDATE ON sales.orders TO 'app'@'%' WITH GRANT OPTION"],
database: "SALES",
table: "orders",
availablePrivileges,
}),
).toEqual({ privileges: ["SELECT", "INSERT", "UPDATE"], grantOption: true });
});
it("supports escaped backticks and ignores grants for other scopes or routines", () => {
expect(
mysqlPrivilegeSelectionFromGrants({
grants: ["GRANT SELECT ON `tenant``db`.`audit``log` TO 'app'@'%'", "GRANT INSERT ON `tenant``db`.* TO 'app'@'%' WITH GRANT OPTION", "GRANT EXECUTE ON PROCEDURE `tenant``db`.`audit``log` TO 'app'@'%' WITH GRANT OPTION"],
database: "tenant`db",
table: "audit`log",
availablePrivileges,
}),
).toEqual({ privileges: ["SELECT"], grantOption: false });
});
it("returns an empty selection when the current scope has no grant", () => {
expect(
mysqlPrivilegeSelectionFromGrants({
grants: ["GRANT SELECT ON `other`.* TO 'app'@'%' WITH GRANT OPTION"],
database: "current",
table: "*",
availablePrivileges,
}),
).toEqual({ privileges: [], grantOption: false });
});
});
describe("database user admin providers", () => {
it("opts only native MySQL connections into account Host changes", () => {
const legacyNative = resolveDatabaseUserAdminProviderForConnection(connection("mysql"));
const blankLegacyNative = resolveDatabaseUserAdminProviderForConnection(connection("mysql", " "));
const native = resolveDatabaseUserAdminProviderForConnection(connection("mysql", "mysql"));
const normalizedNative = resolveDatabaseUserAdminProviderForConnection(connection("mysql", " MySQL "));
expect(legacyNative).toBe(nativeMysqlUserAdminProvider);
expect(blankLegacyNative).toBe(nativeMysqlUserAdminProvider);
expect(native).toBe(nativeMysqlUserAdminProvider);
expect(normalizedNative).toBe(nativeMysqlUserAdminProvider);
expect(native?.renameUserSql?.({ user: "app'user", host: "old\\host" }, "new'\\host")).toBe("RENAME USER 'app''user'@'old\\\\host' TO 'app''user'@'new''\\\\host';");
});
it("does not expose account Host changes to MySQL-compatible providers", () => {
const jdbcMysql = { ...connection("jdbc", "mysql"), connection_string: "jdbc:mysql://localhost:3306/app" };
expect(getDatabaseUserAdminProvider("mysql")?.renameUserSql).toBeUndefined();
expect(resolveDatabaseUserAdminProviderForConnection(connection("goldendb", "goldendb"))?.renameUserSql).toBeUndefined();
expect(resolveDatabaseUserAdminProviderForConnection(jdbcMysql)?.renameUserSql).toBeUndefined();
for (const profile of ["mariadb", "oceanbase", "tidb", "tdsql", "polardb", "greatsql", "goldendb", "doris", "selectdb", "starrocks"]) {
expect(resolveDatabaseUserAdminProviderForConnection(connection("mysql", profile))?.renameUserSql, profile).toBeUndefined();
}
for (const dbType of ["gbase", "doris", "starrocks", "postgres"] as const) {
expect(resolveDatabaseUserAdminProviderForConnection(connection(dbType, dbType))?.renameUserSql, dbType).toBeUndefined();
}
});
it("selects the dedicated provider for GaussDB M-mode connections", () => {
const provider = resolveDatabaseUserAdminProviderForConnection(connection("gaussdb", "GAUSSDB-M"));
expect(provider).toBe(gaussdbMUserAdminProvider);
});
it("quotes role result aliases for M-mode and PostgreSQL-compatible providers", () => {
const gaussdbMSql = gaussdbMUserAdminProvider.listUsersSql();
const postgresSql = postgresUserAdminProvider.listUsersSql();
const kingbaseSql = kingbaseUserAdminProvider.listUsersSql();
expect(gaussdbMSql).toContain("r.rolname AS `user`");
expect(gaussdbMSql).toContain("END AS `host`");
expect(gaussdbMSql).toContain(") AS `plugin`");
for (const sql of [postgresSql, kingbaseSql]) {
expect(sql).toContain('r.rolname AS "user"');
expect(sql).toContain('END AS "host"');
expect(sql).toContain(') AS "plugin"');
}
});
it("uses M-mode-compatible table grant SQL", () => {
const sql = gaussdbMUserAdminProvider.showGrantsSql({ user: "role'o", host: "LOGIN" });
expect(sql).toContain("FROM information_schema.table_privileges");
expect(sql).toContain("concat('Role: '");
expect(sql).toContain("AS `rolbypassrls`");
expect(sql).toContain("AS `sort`");
expect(sql).toContain("AS `line`");
expect(sql).toContain("ORDER BY `sort`, `line`");
expect(sql).toContain("WHERE r.rolname = 'role''o'");
expect(sql).not.toContain("role_table_grants");
expect(sql).not.toContain(" || ");
});
it("keeps native GaussDB, PostgreSQL, and Kingbase providers unchanged", () => {
expect(resolveDatabaseUserAdminProviderForConnection(connection("gaussdb"))).toBe(postgresUserAdminProvider);
expect(resolveDatabaseUserAdminProviderForConnection(connection("postgres"))).toBe(postgresUserAdminProvider);
expect(resolveDatabaseUserAdminProviderForConnection(connection("kingbase"))).toBe(kingbaseUserAdminProvider);
expect(getDatabaseUserAdminProvider("gaussdb")).toBe(postgresUserAdminProvider);
});
it("uses Doris 2.x user and privilege syntax", () => {
const provider = getDatabaseUserAdminProvider("doris");
expect(provider?.listUsersSql()).toBe("SHOW ALL GRANTS;");
expect(provider?.fallbackListUsersSql?.()).toBe("SHOW GRANTS;");
expect(provider?.parseFallbackUsers).toBe(dorisUsersResult);
expect(provider?.showGrantsSql({ user: "reporter", host: "%" })).toBe("SHOW GRANTS FOR 'reporter'@'%';");
expect(provider?.alterPasswordSql?.({ user: "reporter", host: "%" }, "new'secret")).toBe("SET PASSWORD FOR 'reporter'@'%' = PASSWORD('new''secret');");
expect(dorisPrivilegeTargetSql("analytics", "daily`rollup")).toBe("`internal`.`analytics`.`daily``rollup`");
expect(dorisGrantPrivilegesSql({ user: { user: "reporter", host: "%" }, privileges: ["select_priv", "LOAD_PRIV"], database: "analytics", grantOption: true })).toBe("GRANT SELECT_PRIV, LOAD_PRIV ON `internal`.`analytics`.* TO 'reporter'@'%';");
expect(provider?.alterLoginSql).toBeUndefined();
expect(provider?.privilegesForScope?.("table")).toEqual(["SELECT_PRIV", "LOAD_PRIV", "ALTER_PRIV", "CREATE_PRIV", "DROP_PRIV", "SHOW_VIEW_PRIV"]);
});
it("parses Doris SHOW GRANTS users and structured privileges", () => {
const grants = result(["UserIdentity", "Comment", "Password", "Roles", "GlobalPrivs", "DatabasePrivs", "TablePrivs"], [["'root'@'%'", "ROOT", "No", "operator", "Admin_priv", "internal.analytics: Select_priv", null]]);
expect(dorisUsersResult(grants)).toEqual([{ user: "root", host: "%" }]);
expect(dorisGrantsResult(grants)).toEqual(["Roles: operator", "GlobalPrivs: Admin_priv", "DatabasePrivs: internal.analytics: Select_priv"]);
});
it("syncs loaded grants only for the MySQL provider", () => {
const mysqlProvider = getDatabaseUserAdminProvider("mysql");
const dorisProvider = getDatabaseUserAdminProvider("doris");
const postgresProvider = getDatabaseUserAdminProvider("postgres");
const starrocksProvider = getDatabaseUserAdminProvider("starrocks");
expect(
mysqlProvider?.privilegeSelectionFromGrants?.({
grants: ["GRANT INSERT ON `app`.* TO 'user'@'%'"],
database: "app",
table: "*",
availablePrivileges: mysqlProvider.privilegesForScope?.("mysql") ?? [],
}),
).toEqual({ privileges: ["INSERT"], grantOption: false });
expect(postgresProvider?.privilegeSelectionFromGrants).toBeUndefined();
expect(dorisProvider?.privilegeSelectionFromGrants).toBeUndefined();
expect(starrocksProvider?.privilegeSelectionFromGrants).toBeUndefined();
expect(mysqlProvider?.defaultPrivilegesForScope?.("mysql")).toEqual(["SELECT"]);
expect(postgresProvider?.defaultPrivilegesForScope?.("database")).toEqual(["CONNECT"]);
expect(dorisProvider?.defaultPrivilegesForScope?.("table")).toEqual(["SELECT_PRIV"]);
expect(starrocksProvider?.defaultPrivilegesForScope?.("table")).toEqual(["SELECT"]);
});
it("uses sys_catalog for Kingbase role metadata", () => {
const provider = getDatabaseUserAdminProvider("kingbase");
expect(provider).not.toBeNull();
expect(provider?.dialect).toBe("postgres");
expect(provider?.listUsersSql()).toContain("FROM sys_catalog.sys_roles r");
expect(provider?.listUsersSql()).not.toContain("pg_catalog");
});
it("builds Kingbase grant SQL without PostgreSQL catalog tables", () => {
const sql = kingbaseShowGrantsSql({ user: "role'o", host: "LOGIN" });
expect(sql).toContain("FROM sys_catalog.sys_roles r");
expect(sql).toContain("FROM sys_catalog.sys_auth_members m");
expect(sql).toContain("CROSS JOIN sys_catalog.sys_database d");
expect(sql).toContain("CROSS JOIN sys_catalog.sys_namespace n");
expect(sql).toContain("FROM information_schema.table_privileges");
expect(sql).toContain("CAST(privilege_type AS text)");
expect(sql).toContain("CAST(is_grantable AS text)");
expect(sql).toContain("concat('Table: '");
expect(sql).not.toContain(" || ");
expect(sql).not.toContain("role_table_grants");
expect(sql).toContain("WHERE r.rolname = 'role''o'");
expect(sql).not.toContain("pg_catalog");
expect(sql).not.toContain("pg_roles");
});
});