fix(doris): restore user privilege management
This commit is contained in:
1 parent
c2de49c155
commit
9421a1156e
4 files changed
+162
-2
No files matched your search
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getDatabaseUserAdminProvider, kingbaseShowGrantsSql, mysqlPrivilegeSelectionFromGrants } from "@/lib/database/databaseUserAdmin";
|
||||
import { dorisGrantPrivilegesSql, dorisGrantsResult, dorisPrivilegeTargetSql, dorisUsersResult, getDatabaseUserAdminProvider, kingbaseShowGrantsSql, mysqlPrivilegeSelectionFromGrants } from "@/lib/database/databaseUserAdmin";
|
||||
import type { QueryResult } from "@/types/database";
|
||||
|
||||
function result(columns: string[], rows: QueryResult["rows"]): QueryResult {
|
||||
return { columns, rows, affected_rows: 0, execution_time_ms: 1 };
|
||||
}
|
||||
|
||||
describe("MySQL grant privilege selection", () => {
|
||||
const availablePrivileges = ["SELECT", "INSERT", "UPDATE", "EXECUTE"];
|
||||
@@ -50,8 +55,28 @@ describe("MySQL grant privilege selection", () => {
|
||||
});
|
||||
|
||||
describe("database user admin providers", () => {
|
||||
it("uses Doris 2.x user and privilege syntax", () => {
|
||||
const provider = getDatabaseUserAdminProvider("doris");
|
||||
|
||||
expect(provider?.listUsersSql()).toBe("SHOW ALL GRANTS;");
|
||||
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");
|
||||
|
||||
@@ -64,9 +89,11 @@ describe("database user admin providers", () => {
|
||||
}),
|
||||
).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"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ export const KINGBASE_USER_ADMIN_TYPES = new Set<DatabaseType>(["kingbase"]);
|
||||
export const POSTGRES_USER_ADMIN_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "highgo", "kwdb", "opengauss", "questdb", "vastbase"]);
|
||||
|
||||
export const MYSQL_COMMON_PRIVILEGES = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "INDEX", "REFERENCES", "EXECUTE", "SHOW VIEW", "CREATE VIEW", "CREATE ROUTINE", "ALTER ROUTINE", "TRIGGER", "EVENT", "CREATE TEMPORARY TABLES", "LOCK TABLES"] as const;
|
||||
export const DORIS_TABLE_PRIVILEGES = ["SELECT_PRIV", "LOAD_PRIV", "ALTER_PRIV", "CREATE_PRIV", "DROP_PRIV", "SHOW_VIEW_PRIV"] as const;
|
||||
export const STARROCKS_TABLE_PRIVILEGES = ["SELECT", "INSERT", "UPDATE", "DELETE", "ALTER", "DROP", "EXPORT", "ALL"] as const;
|
||||
|
||||
export const POSTGRES_DATABASE_PRIVILEGES = ["CONNECT", "CREATE", "TEMPORARY"] as const;
|
||||
@@ -111,6 +112,30 @@ export function starrocksListUsersSql(): string {
|
||||
return "SHOW USERS;";
|
||||
}
|
||||
|
||||
export function dorisListUsersSql(): string {
|
||||
return "SHOW ALL GRANTS;";
|
||||
}
|
||||
|
||||
export function dorisUsersResult(result: QueryResult): DatabaseUserIdentity[] {
|
||||
const userIndex = columnIndex(result, "UserIdentity");
|
||||
if (userIndex < 0) return [];
|
||||
return result.rows.flatMap((row) => {
|
||||
const parsed = parseMySqlGrantee(String(row[userIndex] ?? ""));
|
||||
return parsed ? [parsed] : [];
|
||||
});
|
||||
}
|
||||
|
||||
export function dorisGrantsResult(result: QueryResult): string[] {
|
||||
const ignoredColumns = new Set(["useridentity", "comment", "password"]);
|
||||
return result.rows.flatMap((row) =>
|
||||
result.columns.flatMap((column, index) => {
|
||||
if (ignoredColumns.has(column.toLowerCase())) return [];
|
||||
const value = String(row[index] ?? "").trim();
|
||||
return value && value.toUpperCase() !== "NULL" ? [`${column}: ${value}`] : [];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function starrocksUsersResult(result: QueryResult): DatabaseUserIdentity[] {
|
||||
const userIndex = columnIndex(result, "user", "User");
|
||||
if (userIndex < 0) return [];
|
||||
@@ -138,6 +163,10 @@ export function mysqlAlterUserPasswordSql(user: DatabaseUserIdentity, password:
|
||||
return `ALTER USER ${mysqlUserAccount(user)} IDENTIFIED BY ${quoteMySqlString(password)};`;
|
||||
}
|
||||
|
||||
export function dorisAlterUserPasswordSql(user: DatabaseUserIdentity, password: string): string {
|
||||
return `SET PASSWORD FOR ${mysqlUserAccount(user)} = PASSWORD(${quoteMySqlString(password)});`;
|
||||
}
|
||||
|
||||
export function mysqlAlterUserAccountLockSql(user: DatabaseUserIdentity, locked: boolean): string {
|
||||
return `ALTER USER ${mysqlUserAccount(user)} ACCOUNT ${locked ? "LOCK" : "UNLOCK"};`;
|
||||
}
|
||||
@@ -186,6 +215,25 @@ export function starrocksRevokePrivilegesSql(input: PrivilegeChangeInput): strin
|
||||
return `REVOKE ${privileges} ON ${starrocksPrivilegeTargetSql(input.database, input.table)} FROM USER ${mysqlUserAccount(input.user)};`;
|
||||
}
|
||||
|
||||
export function dorisPrivilegeTargetSql(database: string, table = "*"): string {
|
||||
const db = database.trim() || "*";
|
||||
const tbl = table.trim() || "*";
|
||||
if (db === "*") return "*.*.*";
|
||||
const tableSql = tbl === "*" ? "*" : quoteMySqlIdentifier(tbl);
|
||||
return `${quoteMySqlIdentifier("internal")}.${quoteMySqlIdentifier(db)}.${tableSql}`;
|
||||
}
|
||||
|
||||
export function dorisGrantPrivilegesSql(input: PrivilegeChangeInput): string {
|
||||
const privileges = normalizePrivileges(input.privileges, "SELECT_PRIV").join(", ");
|
||||
// Doris 2.x GRANT has no WITH GRANT OPTION clause; GRANT_PRIV is managed as an explicit privilege instead.
|
||||
return `GRANT ${privileges} ON ${dorisPrivilegeTargetSql(input.database, input.table)} TO ${mysqlUserAccount(input.user)};`;
|
||||
}
|
||||
|
||||
export function dorisRevokePrivilegesSql(input: PrivilegeChangeInput): string {
|
||||
const privileges = normalizePrivileges(input.privileges, "SELECT_PRIV").join(", ");
|
||||
return `REVOKE ${privileges} ON ${dorisPrivilegeTargetSql(input.database, input.table)} FROM ${mysqlUserAccount(input.user)};`;
|
||||
}
|
||||
|
||||
export function normalizePrivileges(privileges: string[], fallback = "SELECT"): string[] {
|
||||
const normalized = privileges.map((privilege) => privilege.trim().toUpperCase()).filter(Boolean);
|
||||
return Array.from(new Set(normalized.length > 0 ? normalized : [fallback]));
|
||||
@@ -582,6 +630,24 @@ export const kingbaseUserAdminProvider: DatabaseUserAdminProvider = {
|
||||
showGrantsSql: kingbaseShowGrantsSql,
|
||||
};
|
||||
|
||||
export const dorisUserAdminProvider: DatabaseUserAdminProvider = {
|
||||
dialect: "mysql",
|
||||
defaultScope: "table",
|
||||
listUsersSql: dorisListUsersSql,
|
||||
parseUsers: dorisUsersResult,
|
||||
showGrantsSql: mysqlShowGrantsSql,
|
||||
parseGrants: dorisGrantsResult,
|
||||
createUserSql: mysqlCreateUserSql,
|
||||
alterPasswordSql: dorisAlterUserPasswordSql,
|
||||
dropUserSql: mysqlDropUserSql,
|
||||
grantPrivilegesSql: dorisGrantPrivilegesSql,
|
||||
revokePrivilegesSql: dorisRevokePrivilegesSql,
|
||||
label: mysqlUserLabel,
|
||||
detail: (user) => user.plugin,
|
||||
privilegesForScope: () => DORIS_TABLE_PRIVILEGES,
|
||||
defaultPrivilegesForScope: () => ["SELECT_PRIV"],
|
||||
};
|
||||
|
||||
export const starrocksUserAdminProvider: DatabaseUserAdminProvider = {
|
||||
dialect: "mysql",
|
||||
defaultScope: "table",
|
||||
@@ -603,6 +669,7 @@ export const starrocksUserAdminProvider: DatabaseUserAdminProvider = {
|
||||
const DATABASE_USER_ADMIN_PROVIDER_BY_TYPE = new Map<DatabaseType, DatabaseUserAdminProvider>([
|
||||
["mysql", mysqlUserAdminProvider],
|
||||
["goldendb", mysqlUserAdminProvider],
|
||||
["doris", dorisUserAdminProvider],
|
||||
["kingbase", kingbaseUserAdminProvider],
|
||||
["postgres", postgresUserAdminProvider],
|
||||
["gaussdb", postgresUserAdminProvider],
|
||||
|
||||
Reference in new issue
Block a user