feat: add integration access settings (#725)

* feat: add integration access settings

* fix: typecheck and test issues

* fix: test timeout

* chore: address pull request feedback

* chore: add throw if action forbidden for integration permissions

* fix: unable to create new migrations because of duplicate prevId in sqlite snapshots

* chore: add sqlite migration for integration permissions

* test: add unit tests for integration access

* test: add permission checks to integration router tests

* test: add unit test for integration permissions

* chore: add mysql migration

* fix: format issues
This commit is contained in:
Meier Lukas
2024-07-08 00:00:37 +02:00
committed by GitHub
parent be711149f7
commit 408cdeb5c3
50 changed files with 4392 additions and 615 deletions

View File

@@ -1,23 +1,57 @@
import { objectEntries, objectKeys } from "@homarr/common";
export const boardPermissions = ["board-view", "board-change"] as const;
/**
* Permissions for boards.
* view: Can view the board and its content. (e.g. see all items on the board, but not modify them)
* modify: Can modify the board, its content and visual settings. (e.g. move items, change the background)
* full: Can modify the board, its content, visual settings, access settings, delete, change the visibility and rename. (e.g. change the board name, delete the board, give access to other users)
*/
export const boardPermissions = ["view", "modify", "full"] as const;
export const boardPermissionsMap = {
view: "board-view-all",
modify: "board-modify-all",
full: "board-full-all",
} satisfies Record<BoardPermission, GroupPermissionKey>;
export type BoardPermission = (typeof boardPermissions)[number];
/**
* Permissions for integrations.
* use: Can select the integration for an item on the board. (e.g. select pi-hole for a widget)
* interact: Can interact with the integration. (e.g. enable / disable pi-hole)
* full: Can modify the integration. (e.g. change the pi-hole url, secrets and access settings)
*/
export const integrationPermissions = ["use", "interact", "full"] as const;
export const integrationPermissionsMap = {
use: "integration-use-all",
interact: "integration-interact-all",
full: "integration-full-all",
} satisfies Record<IntegrationPermission, GroupPermissionKey>;
export type IntegrationPermission = (typeof integrationPermissions)[number];
/**
* Global permissions that can be assigned to groups.
* The keys are generated through combining the key and all array items.
* For example "board-create" is a generated key
*/
export const groupPermissions = {
board: ["create", "view-all", "modify-all", "full-access"],
integration: ["create", "use-all", "interact-all", "full-access"],
board: ["create", "view-all", "modify-all", "full-all"],
integration: ["create", "use-all", "interact-all", "full-all"],
admin: true,
} as const;
/**
* In the following object is described how the permissions are related to each other.
* For example everybody with the permission "board-modify-all" also has the permission "board-view-all".
* Or admin has all permissions (board-full-access and integration-full-access which will resolve in an array of every permission).
* Or admin has all permissions (board-full-all and integration-full-all which will resolve in an array of every permission).
*/
const groupPermissionParents = {
"board-modify-all": ["board-view-all"],
"board-full-access": ["board-modify-all", "board-create"],
"board-full-all": ["board-modify-all", "board-create"],
"integration-interact-all": ["integration-use-all"],
"integration-full-access": ["integration-interact-all", "integration-create"],
admin: ["board-full-access", "integration-full-access"],
"integration-full-all": ["integration-interact-all", "integration-create"],
admin: ["board-full-all", "integration-full-all"],
} satisfies Partial<Record<GroupPermissionKey, GroupPermissionKey[]>>;
export const getPermissionsWithParents = (permissions: GroupPermissionKey[]): GroupPermissionKey[] => {
@@ -66,5 +100,3 @@ export const groupPermissionKeys = objectKeys(groupPermissions).reduce((acc, key
}
return acc;
}, [] as GroupPermissionKey[]);
export type BoardPermission = (typeof boardPermissions)[number];

View File

@@ -5,14 +5,14 @@ import { getPermissionsWithChildren, getPermissionsWithParents } from "../permis
describe("getPermissionsWithParents should return the correct permissions", () => {
test.each([
[["board-view-all"], ["board-view-all", "board-modify-all", "board-full-access", "admin"]],
[["board-modify-all"], ["board-modify-all", "board-full-access", "admin"]],
[["board-create"], ["board-create", "board-full-access", "admin"]],
[["board-full-access"], ["board-full-access", "admin"]],
[["integration-use-all"], ["integration-use-all", "integration-interact-all", "integration-full-access", "admin"]],
[["integration-create"], ["integration-create", "integration-full-access", "admin"]],
[["integration-interact-all"], ["integration-interact-all", "integration-full-access", "admin"]],
[["integration-full-access"], ["integration-full-access", "admin"]],
[["board-view-all"], ["board-view-all", "board-modify-all", "board-full-all", "admin"]],
[["board-modify-all"], ["board-modify-all", "board-full-all", "admin"]],
[["board-create"], ["board-create", "board-full-all", "admin"]],
[["board-full-all"], ["board-full-all", "admin"]],
[["integration-use-all"], ["integration-use-all", "integration-interact-all", "integration-full-all", "admin"]],
[["integration-create"], ["integration-create", "integration-full-all", "admin"]],
[["integration-interact-all"], ["integration-interact-all", "integration-full-all", "admin"]],
[["integration-full-all"], ["integration-full-all", "admin"]],
[["admin"], ["admin"]],
] satisfies [GroupPermissionKey[], GroupPermissionKey[]][])("expect %s to return %s", (input, expectedOutput) => {
expect(getPermissionsWithParents(input)).toEqual(expect.arrayContaining(expectedOutput));
@@ -24,19 +24,19 @@ describe("getPermissionsWithChildren should return the correct permissions", ()
[["board-view-all"], ["board-view-all"]],
[["board-modify-all"], ["board-view-all", "board-modify-all"]],
[["board-create"], ["board-create"]],
[["board-full-access"], ["board-full-access", "board-modify-all", "board-view-all"]],
[["board-full-all"], ["board-full-all", "board-modify-all", "board-view-all"]],
[["integration-use-all"], ["integration-use-all"]],
[["integration-create"], ["integration-create"]],
[["integration-interact-all"], ["integration-interact-all", "integration-use-all"]],
[["integration-full-access"], ["integration-full-access", "integration-interact-all", "integration-use-all"]],
[["integration-full-all"], ["integration-full-all", "integration-interact-all", "integration-use-all"]],
[
["admin"],
[
"admin",
"board-full-access",
"board-full-all",
"board-modify-all",
"board-view-all",
"integration-full-access",
"integration-full-all",
"integration-interact-all",
"integration-use-all",
],