Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions electron/src/apps/local-app-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function dbxUrl(payload) {
}

function valuesFor(payload): Record<string, string> {
const dmConnection = `${username(payload)}/${String(payload.token?.value || "")}@${String(payload.endpoint?.host || "")}:${String(payload.endpoint?.port || "")}`;
return {
name: sanitizedName(payload.name),
protocol: payload.protocol,
Expand All @@ -55,6 +56,7 @@ function valuesFor(payload): Record<string, string> {
host: String(payload.endpoint?.host || ""),
port: String(payload.endpoint?.port || ""),
dbname: payload.protocol === "oracle" ? username(payload) : String(payload.asset?.info?.db_name || ""),
dameng_disql: shellQuote(dmConnection),
dbeaver_protocol: payload.protocol === "sqlserver" ? "mssql_jdbc_ms_new" : payload.protocol,
dbx_url: dbxUrl(payload),
url: navicatUrl(payload)
Expand Down
83 changes: 83 additions & 0 deletions electron/tests/application-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,89 @@ for (const platform of ["macos", "linux", "windows"]) {
});
}

for (const platform of ["macos", "linux", "windows"]) {
test(`${platform} offers DBeaver for Dameng and launches its native driver`, async (context) => {
const appData = await mkdtemp(path.join(os.tmpdir(), "jms-dameng-dbeaver-"));
context.after(() => rm(appData, { recursive: true, force: true }));
const app = { getPath: () => appData, isPackaged: false };
const service = new ApplicationConfigService(app, projectRoot);
service.builtInDir = async () => path.join(projectRoot, "plugins", platform);
await service.initialize();
await service.updateSelection({
category: "databases",
protocol: "dameng",
name: "dbeaver",
pluginId: undefined,
path: process.execPath
});
await service.updateSelection({
category: "databases",
protocol: "dameng",
name: "dbeaver",
pluginId: undefined,
path: undefined,
makeDefault: true
});
const application = (await service.getConfig()).databases.find((item) => item.name === "dbeaver");
assert.ok(application?.protocol.includes("dameng"));
assert.ok(application.match_first.includes("dameng"));

const launcher = new LocalApplicationLauncher(app, projectRoot, service, null);
let launched = false;
launcher.launchExecutable = async (selected, argumentString) => {
assert.equal(selected.name, "dbeaver");
assert.deepEqual(localAppLauncherInternals.splitArguments(argumentString), [
"-con",
"name=Dameng|driver=dameng|user=token-id|password=secret|host=gateway.example.com|port=5525|save=false|connect=true"
]);
launched = true;
};
const payload = {
protocol: "dameng",
name: "Dameng",
endpoint: { host: "gateway.example.com", port: 5525 },
token: { id: "token-id", value: "secret" },
asset: { info: { db_name: "business" } }
};
await launcher.launch(`jms2://${Buffer.from(JSON.stringify(payload)).toString("base64")}`);
assert.equal(launched, true);
});
}

for (const platform of ["macos", "linux"]) {
test(`${platform} offers a shell-safe Dameng disql launch`, async (context) => {
const appData = await mkdtemp(path.join(os.tmpdir(), "jms-dameng-disql-"));
context.after(() => rm(appData, { recursive: true, force: true }));
const app = { getPath: () => appData, isPackaged: false };
const service = new ApplicationConfigService(app, projectRoot);
service.builtInDir = async () => path.join(projectRoot, "plugins", platform);
await service.initialize();
await service.updateSelection({
category: "databases",
protocol: "dameng",
name: "terminal",
pluginId: undefined,
path: undefined,
makeDefault: true
});
const launcher = new LocalApplicationLauncher(app, projectRoot, service, null);
let launched = false;
launcher.launchTerminal = async (selected, command) => {
assert.equal(selected.name, "terminal");
assert.equal(command, "disql 'token-id/p'\\''a&b@gateway.example.com:5525'");
launched = true;
};
const payload = {
protocol: "dameng",
name: "Dameng",
endpoint: { host: "gateway.example.com", port: 5525 },
token: { id: "token-id", value: "p'a&b" }
};
await launcher.launch(`jms2://${Buffer.from(JSON.stringify(payload)).toString("base64")}`);
assert.equal(launched, true);
});
}

for (const [platform, protocol, expectedClient] of [
["macos", "ssh", "terminal"],
["linux", "ssh", "terminal"],
Expand Down
5 changes: 4 additions & 1 deletion plugins/linux/linux.dbeaver/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
},
"launch": {
"type": "args",
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true"
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true",
"protocol_templates": {
"dameng": "-con name={name}|driver=dameng|user={username}|password={value}|host={host}|port={port}|save=false|connect=true"

Check warning on line 16 in plugins/linux/linux.dbeaver/connect.json

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"password" detected here, make sure this is not a hard-coded credential.

See more on https://sonarcloud.io/project/issues?id=jumpserver_luna&issues=AaEgAc9ds2OjFVrU646u&open=AaEgAc9ds2OjFVrU646u&pullRequest=1748
}
},
"display_name": "DBeaver Community"
}
1 change: 1 addition & 0 deletions plugins/linux/linux.dbeaver/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"mariadb",
"mysql",
"oracle",
"dameng",
"postgresql",
"sqlserver"
],
Expand Down
1 change: 1 addition & 0 deletions plugins/linux/linux.terminal-db/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"protocol_templates": {
"redis": "redis-cli -h {host} -p {port} -a {username}@{value}",
"oracle": "sqlplus {username}/{value}@{host}:{port}/{dbname}",
"dameng": "disql {dameng_disql}",
"postgresql": "psql 'user={username} password={value} host={host} dbname={dbname} port={port}'",
"mysql": "mysql -u {username} -p{value} -h {host} -P {port} {dbname}",
"mariadb": "mysql -u {username} -p{value} -h {host} -P {port} {dbname}",
Expand Down
1 change: 1 addition & 0 deletions plugins/linux/linux.terminal-db/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"mongodb",
"mysql",
"oracle",
"dameng",
"postgresql",
"redis",
"sqlserver"
Expand Down
5 changes: 4 additions & 1 deletion plugins/macos/macos.dbeaver/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
},
"launch": {
"type": "args",
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true"
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true",
"protocol_templates": {
"dameng": "-con name={name}|driver=dameng|user={username}|password={value}|host={host}|port={port}|save=false|connect=true"

Check warning on line 16 in plugins/macos/macos.dbeaver/connect.json

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"password" detected here, make sure this is not a hard-coded credential.

See more on https://sonarcloud.io/project/issues?id=jumpserver_luna&issues=AaEgAc0ds2OjFVrU646s&open=AaEgAc0ds2OjFVrU646s&pullRequest=1748
}
},
"display_name": "DBeaver Community"
}
1 change: 1 addition & 0 deletions plugins/macos/macos.dbeaver/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"mariadb",
"mysql",
"oracle",
"dameng",
"postgresql",
"sqlserver"
],
Expand Down
1 change: 1 addition & 0 deletions plugins/macos/macos.terminal-db/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"protocol_templates": {
"redis": "redis-cli -h {host} -p {port} -a {username}@{value}",
"oracle": "sqlplus {username}/{value}@{host}:{port}/{dbname}",
"dameng": "disql {dameng_disql}",
"postgresql": "psql 'user={username} password={value} host={host} dbname={dbname} port={port}'",
"mysql": "mysql -u {username} -p{value} -h {host} -P {port} {dbname}",
"mariadb": "mysql -u {username} -p{value} -h {host} -P {port} {dbname}",
Expand Down
1 change: 1 addition & 0 deletions plugins/macos/macos.terminal-db/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"mongodb",
"mysql",
"oracle",
"dameng",
"postgresql",
"redis",
"sqlserver"
Expand Down
5 changes: 4 additions & 1 deletion plugins/windows/windows.dbeaver/connect.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
},
"launch": {
"type": "args",
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true"
"template": "-con name={name}|driver={dbeaver_protocol}|user={username}|password={value}|database={dbname}|host={host}|port={port}|save=false|connect=true",
"protocol_templates": {
"dameng": "-con name={name}|driver=dameng|user={username}|password={value}|host={host}|port={port}|save=false|connect=true"

Check warning on line 16 in plugins/windows/windows.dbeaver/connect.json

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"password" detected here, make sure this is not a hard-coded credential.

See more on https://sonarcloud.io/project/issues?id=jumpserver_luna&issues=AaEgAc8bs2OjFVrU646t&open=AaEgAc8bs2OjFVrU646t&pullRequest=1748
}
},
"display_name": "DBeaver Community"
}
1 change: 1 addition & 0 deletions plugins/windows/windows.dbeaver/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"mariadb",
"mysql",
"oracle",
"dameng",
"postgresql",
"sqlserver"
],
Expand Down
35 changes: 35 additions & 0 deletions ui/composables/useAssetAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,41 @@ describe("opening assets in local applications", () => {
});
});

it("passes Dameng proxy credentials to the selected desktop client", async () => {
vi.stubGlobal("isDesktopRuntime", () => true);
const dbMethod = { value: "db_client", type: "native", component: "magnus", disabled: false };
vi.stubGlobal("useConnectMethods", () => ({
fetchConnectMethods: async () => ({ dameng: [dbMethod] }),
getMethodsForProtocol: async () => [dbMethod]
}));
mocks.appConfig.value = {
databases: [{ name: "dbeaver", protocol: ["dameng"], is_set: true, path_exists: true, match_first: ["dameng"] }]
};
const dbPayload = {
...payload,
protocol: "dameng",
endpoint: { host: "gateway.example.com", port: 5525 },
token: { ...payload.token, protocol: "dameng" }
};
mocks.getLocalClientUrl.mockResolvedValue({
url: `jms2://${btoa(String.fromCharCode(...new TextEncoder().encode(JSON.stringify(dbPayload))))}`
});
mocks.createToken.mockResolvedValue(dbPayload.token);

const { failed } = await connect("native_app:db_client:dbeaver", "dameng");

expect(failed).not.toHaveBeenCalled();
expect(mocks.createToken).toHaveBeenCalledWith(
expect.objectContaining({ protocol: "dameng", connect_method: "db_client" }),
expect.anything()
);
const launchedUrl = mocks.invoke.mock.calls[0]?.[1].url;
const launchedPayload = JSON.parse(
new TextDecoder().decode(Uint8Array.from(atob(launchedUrl.slice(7)), (character) => character.charCodeAt(0)))
);
expect(launchedPayload).toEqual({ ...dbPayload, client: "dbeaver" });
});

it("preserves an applet's RDP launch protocol for a MariaDB asset", async () => {
const applet = { value: "dbeaver", type: "applet", component: "razor", disabled: false };
vi.stubGlobal("useConnectMethods", () => ({
Expand Down
4 changes: 3 additions & 1 deletion ui/pages/setting/application.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const localePath = useLocalePath();
const { activeApplicationProtocol } = useSettingsWindow();
const { pluginList, installPlugin, uninstallPlugin } = useApplicationConfig();
const { language } = useSettingManager();
const HIDDEN_DATABASE_PROTOCOLS = new Set(["mongodb", "oracle"]);
const HIDDEN_DATABASE_PROTOCOLS = new Set(["mongodb", "oracle", "dameng"]);
const appName = getConfiguredAppName();
const pluginModalOpen = ref(false);
const installingPlugin = ref(false);
Expand All @@ -32,6 +32,7 @@ const protocolComponents = {
redis: defineAsyncComponent(() => import("~/pages/setting/application/redis.vue")),
pg: defineAsyncComponent(() => import("~/pages/setting/application/pg.vue")),
oracle: defineAsyncComponent(() => import("~/pages/setting/application/oracle.vue")),
dameng: defineAsyncComponent(() => import("~/pages/setting/application/dameng.vue")),
sqlserver: defineAsyncComponent(() => import("~/pages/setting/application/sqlserver.vue"))
} as const;
Expand Down Expand Up @@ -62,6 +63,7 @@ const appMenu = computed<NavigationMenuItem[]>(() => {
{ label: "Redis", protocol: "redis" as const, routeName: "setting-application-redis" },
{ label: "PostgreSQL", protocol: "pg" as const, routeName: "setting-application-pg" },
{ label: "Oracle", protocol: "oracle" as const, routeName: "setting-application-oracle" },
{ label: "Dameng", protocol: "dameng" as const, routeName: "setting-application-dameng" },
{ label: "SQL Server", protocol: "sqlserver" as const, routeName: "setting-application-sqlserver" }
]
.filter((item) => isDefaultAppName(appName) || !HIDDEN_DATABASE_PROTOCOLS.has(item.protocol))
Expand Down
7 changes: 7 additions & 0 deletions ui/pages/setting/application/dameng.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
import AppConfigProtocolList from "~/components/SettingItems/appConfigProtocolList.vue";
</script>

<template>
<AppConfigProtocolList category="databases" protocol="dameng" />
</template>
Loading