mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: namespace plugin permissions and keep them valid when a plugin is off
This commit is contained in:
1 parent
8431570ff7
commit
cc15559ea1
53 files changed
+31442
-351
No files matched your search
+31
-12
@@ -56,19 +56,38 @@
|
||||
"kind": "button"
|
||||
}
|
||||
],
|
||||
"permissionGroup": {
|
||||
"group": "ai",
|
||||
"permissions": [
|
||||
"ai.use",
|
||||
"ai.manage_providers",
|
||||
"ai.apply_proposals",
|
||||
"ai.services.use",
|
||||
"ai.secrets.share"
|
||||
],
|
||||
"defaultForRole": {
|
||||
"admin": ["ai.services.use"]
|
||||
"permissions": [
|
||||
{
|
||||
"name": "use",
|
||||
"titleKey": "permissions.use.title",
|
||||
"descriptionKey": "permissions.use.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "manage_providers",
|
||||
"titleKey": "permissions.manage_providers.title",
|
||||
"descriptionKey": "permissions.manage_providers.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "apply_proposals",
|
||||
"titleKey": "permissions.apply_proposals.title",
|
||||
"descriptionKey": "permissions.apply_proposals.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "services.use",
|
||||
"titleKey": "permissions.services.use.title",
|
||||
"descriptionKey": "permissions.services.use.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "secrets.share",
|
||||
"titleKey": "permissions.secrets.share.title",
|
||||
"descriptionKey": "permissions.secrets.share.description",
|
||||
"defaultRoles": ["user"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"locales": "locales",
|
||||
"platforms": ["linux", "win32", "darwin"]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseManifest, qualifyPermission } from "@termix/plugin-sdk/manifest";
|
||||
|
||||
/**
|
||||
* The permission ids an admin grants.
|
||||
*
|
||||
* Core registers each declared name as `<pluginId>.<name>`, so these strings
|
||||
* are what end up stored in a role. They have to keep matching what ai
|
||||
* shipped before the permissions moved out of the core catalog, or every role
|
||||
* holding one would silently stop working.
|
||||
*/
|
||||
const EXPECTED = [
|
||||
"ai.use",
|
||||
"ai.manage_providers",
|
||||
"ai.apply_proposals",
|
||||
"ai.services.use",
|
||||
"ai.secrets.share",
|
||||
];
|
||||
|
||||
describe("ai permissions", () => {
|
||||
const manifest = parseManifest(
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve(import.meta.dirname, "../../manifest.json"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
it("parses cleanly", () => {
|
||||
expect(manifest.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("registers exactly the ids roles already hold", () => {
|
||||
const ids = (manifest.manifest?.contributes?.permissions ?? []).map(
|
||||
(permission) => qualifyPermission("ai", permission.name),
|
||||
);
|
||||
|
||||
expect(ids).toEqual(EXPECTED);
|
||||
});
|
||||
|
||||
it("declares a title and description for every permission", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.titleKey).toBeTruthy();
|
||||
expect(permission.descriptionKey).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
// Core used to grant these through SYSTEM_ROLE_DEFAULTS, so a fresh install
|
||||
// has to keep giving them to the user role.
|
||||
it("keeps the fresh-install defaults core used to seed", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.defaultRoles).toEqual(["user"]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,40 @@
|
||||
"network:serve",
|
||||
"ui:surface"
|
||||
],
|
||||
"contributes": {
|
||||
"permissions": [
|
||||
{
|
||||
"name": "view",
|
||||
"titleKey": "permissions.view.title",
|
||||
"descriptionKey": "permissions.view.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "create",
|
||||
"titleKey": "permissions.create.title",
|
||||
"descriptionKey": "permissions.create.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "edit",
|
||||
"titleKey": "permissions.edit.title",
|
||||
"descriptionKey": "permissions.edit.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "delete",
|
||||
"titleKey": "permissions.delete.title",
|
||||
"descriptionKey": "permissions.delete.description",
|
||||
"defaultRoles": ["user"]
|
||||
},
|
||||
{
|
||||
"name": "run",
|
||||
"titleKey": "permissions.run.title",
|
||||
"descriptionKey": "permissions.run.description",
|
||||
"defaultRoles": ["user"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"locales": "locales",
|
||||
"platforms": ["linux", "win32", "darwin"]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseManifest, qualifyPermission } from "@termix/plugin-sdk/manifest";
|
||||
|
||||
/**
|
||||
* The permission ids an admin grants.
|
||||
*
|
||||
* Core registers each declared name as `<pluginId>.<name>`, so these strings
|
||||
* are what end up stored in a role. They have to keep matching what automations
|
||||
* shipped before the permissions moved out of the core catalog, or every role
|
||||
* holding one would silently stop working.
|
||||
*/
|
||||
const EXPECTED = [
|
||||
"automations.view",
|
||||
"automations.create",
|
||||
"automations.edit",
|
||||
"automations.delete",
|
||||
"automations.run",
|
||||
];
|
||||
|
||||
describe("automations permissions", () => {
|
||||
const manifest = parseManifest(
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve(import.meta.dirname, "../../manifest.json"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
it("parses cleanly", () => {
|
||||
expect(manifest.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("registers exactly the ids roles already hold", () => {
|
||||
const ids = (manifest.manifest?.contributes?.permissions ?? []).map(
|
||||
(permission) => qualifyPermission("automations", permission.name),
|
||||
);
|
||||
|
||||
expect(ids).toEqual(EXPECTED);
|
||||
});
|
||||
|
||||
it("declares a title and description for every permission", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.titleKey).toBeTruthy();
|
||||
expect(permission.descriptionKey).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
// Core used to grant these through SYSTEM_ROLE_DEFAULTS, so a fresh install
|
||||
// has to keep giving them to the user role.
|
||||
it("keeps the fresh-install defaults core used to seed", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.defaultRoles).toEqual(["user"]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,11 @@
|
||||
export const id = "network-topology";
|
||||
export const tabId = "network_graph";
|
||||
|
||||
export async function register({ registerRailItem, registerTabComponent, icons }) {
|
||||
export async function register({
|
||||
registerRailItem,
|
||||
registerTabComponent,
|
||||
icons,
|
||||
}) {
|
||||
registerTabComponent?.(tabId, () =>
|
||||
import("./NetworkGraphCard.tsx").then((m) => ({
|
||||
default: m.NetworkGraphCard,
|
||||
@@ -31,7 +35,10 @@ export async function register({ registerRailItem, registerTabComponent, icons }
|
||||
});
|
||||
}
|
||||
|
||||
export async function unregister({ unregisterRailItem, unregisterTabComponent }) {
|
||||
export async function unregister({
|
||||
unregisterRailItem,
|
||||
unregisterTabComponent,
|
||||
}) {
|
||||
unregisterRailItem?.(tabId);
|
||||
unregisterTabComponent?.(tabId);
|
||||
}
|
||||
@@ -21,10 +21,13 @@
|
||||
"ui:surface"
|
||||
],
|
||||
"contributes": {
|
||||
"permissionGroup": {
|
||||
"group": "tailscale",
|
||||
"permissions": ["tailscale.devices.view"]
|
||||
}
|
||||
"permissions": [
|
||||
{
|
||||
"name": "devices.view",
|
||||
"titleKey": "permissions.devices.view.title",
|
||||
"descriptionKey": "permissions.devices.view.description"
|
||||
}
|
||||
]
|
||||
},
|
||||
"locales": "locales",
|
||||
"platforms": ["linux", "win32", "darwin"]
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseManifest, qualifyPermission } from "@termix/plugin-sdk/manifest";
|
||||
|
||||
/**
|
||||
* The permission ids an admin grants.
|
||||
*
|
||||
* Core registers each declared name as `<pluginId>.<name>`, so these strings
|
||||
* are what end up stored in a role. They have to keep matching what tailscale
|
||||
* shipped before the permissions moved out of the core catalog, or every role
|
||||
* holding one would silently stop working.
|
||||
*/
|
||||
const EXPECTED = ["tailscale.devices.view"];
|
||||
|
||||
describe("tailscale permissions", () => {
|
||||
const manifest = parseManifest(
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve(import.meta.dirname, "../../manifest.json"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
it("parses cleanly", () => {
|
||||
expect(manifest.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("registers exactly the ids roles already hold", () => {
|
||||
const ids = (manifest.manifest?.contributes?.permissions ?? []).map(
|
||||
(permission) => qualifyPermission("tailscale", permission.name),
|
||||
);
|
||||
|
||||
expect(ids).toEqual(EXPECTED);
|
||||
});
|
||||
|
||||
it("declares a title and description for every permission", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.titleKey).toBeTruthy();
|
||||
expect(permission.descriptionKey).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
// No wildcard covered tailscale in SYSTEM_ROLE_DEFAULTS, so nothing is
|
||||
// granted by default and an admin has to opt in.
|
||||
it("grants nothing by default", () => {
|
||||
for (const permission of manifest.manifest?.contributes?.permissions ??
|
||||
[]) {
|
||||
expect(permission.defaultRoles).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user