From bda70cb5780e93beaec4054d98d014e25f89b794 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 19 Dec 2022 10:02:43 -0800 Subject: [PATCH] core: add support for users and acls --- plugins/core/src/aggregate-core.ts | 3 + plugins/core/src/automations-core.ts | 3 + plugins/core/src/main.ts | 23 +++- plugins/core/src/media-core.ts | 3 + plugins/core/src/script-core.ts | 3 + plugins/core/src/user.ts | 117 ++++++++++++++++++ .../ui/src/components/AggregateComponent.vue | 2 +- plugins/core/ui/src/components/Device.vue | 6 +- .../ui/src/components/ScriptComponent.vue | 2 +- .../automation/AutomationComponent.vue | 2 +- .../components/automation/InterfacePicker.vue | 2 - .../src/components/automation/interfaces.ts | 1 - .../src/components/plugin/PluginComponent.vue | 27 ++-- .../core/ui/src/components/plugin/plugin.ts | 9 +- 14 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 plugins/core/src/user.ts diff --git a/plugins/core/src/aggregate-core.ts b/plugins/core/src/aggregate-core.ts index ab14b39a0..ae930b466 100644 --- a/plugins/core/src/aggregate-core.ts +++ b/plugins/core/src/aggregate-core.ts @@ -66,4 +66,7 @@ export class AggregateCore extends ScryptedDeviceBase implements DeviceProvider, async getDevice(nativeId: string) { return this.aggregate.get(nativeId); } + + async releaseDevice(id: string, nativeId: string): Promise { + } } diff --git a/plugins/core/src/automations-core.ts b/plugins/core/src/automations-core.ts index 54884007f..9fd137b26 100644 --- a/plugins/core/src/automations-core.ts +++ b/plugins/core/src/automations-core.ts @@ -83,4 +83,7 @@ export class AutomationCore extends ScryptedDeviceBase implements DeviceProvider async getDevice(nativeId: string) { return this.automations.get(nativeId); } + + async releaseDevice(id: string, nativeId: string): Promise { + } } diff --git a/plugins/core/src/main.ts b/plugins/core/src/main.ts index 787968d03..ff2a2ae66 100644 --- a/plugins/core/src/main.ts +++ b/plugins/core/src/main.ts @@ -9,6 +9,7 @@ import { AutomationCore, AutomationCoreNativeId } from './automations-core'; import { LauncherMixin } from './launcher-mixin'; import { MediaCore } from './media-core'; import { ScriptCore, ScriptCoreNativeId } from './script-core'; +import { UsersCore, UsersNativeId } from './user'; const { systemManager, deviceManager, endpointManager } = sdk; @@ -23,7 +24,6 @@ interface RoutedHttpRequest extends HttpRequest { params: { [key: string]: string }; } - class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, EngineIOHandler, DeviceProvider, Settings { router: any = Router(); publicRouter: any = Router(); @@ -32,6 +32,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng scriptCore: ScriptCore; aggregateCore: AggregateCore; automationCore: AutomationCore; + users: UsersCore; localAddresses: string[]; storageSettings = new StorageSettings(this, { localAddresses: { @@ -101,6 +102,19 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng ); this.aggregateCore = new AggregateCore(); })(); + + + (async () => { + await deviceManager.onDeviceDiscovered( + { + name: 'Scrypted Users', + nativeId: UsersNativeId, + interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme], + type: ScryptedDeviceType.Builtin, + }, + ); + this.users = new UsersCore(); + })(); } async getSettings(): Promise { @@ -127,9 +141,11 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng return this.automationCore; if (nativeId === AggregateCoreNativeId) return this.aggregateCore; + if (nativeId === UsersNativeId) + return this.users; } - async releaseDevice(id: string, nativeId: string, device: any): Promise { + async releaseDevice(id: string, nativeId: string): Promise { } checkEngineIoEndpoint(request: HttpRequest, name: string) { @@ -140,6 +156,9 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng } async checkService(request: HttpRequest, ws: WebSocket, name: string): Promise { + // only allow admin users to access these services. + if (request.aclId) + return false; const check = this.checkEngineIoEndpoint(request, name); if (!check) return false; diff --git a/plugins/core/src/media-core.ts b/plugins/core/src/media-core.ts index c9c116045..7db47b38c 100644 --- a/plugins/core/src/media-core.ts +++ b/plugins/core/src/media-core.ts @@ -137,4 +137,7 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf if (nativeId === 'files') return this.filesHost; } + + async releaseDevice(id: string, nativeId: string): Promise { + } } \ No newline at end of file diff --git a/plugins/core/src/script-core.ts b/plugins/core/src/script-core.ts index f9658d3e5..41dc8d796 100644 --- a/plugins/core/src/script-core.ts +++ b/plugins/core/src/script-core.ts @@ -87,4 +87,7 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De getDevice(nativeId: string) { return this.scripts.get(nativeId); } + + async releaseDevice(id: string, nativeId: string): Promise { + } } diff --git a/plugins/core/src/user.ts b/plugins/core/src/user.ts new file mode 100644 index 000000000..bb4fd9fcc --- /dev/null +++ b/plugins/core/src/user.ts @@ -0,0 +1,117 @@ +import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedUser, ScryptedUserAccessControl, Setting, Settings, SettingValue } from "@scrypted/sdk"; +import { addAccessControlsForInterface } from "@scrypted/sdk/acl"; +export const UsersNativeId = 'users'; + +type DBUser = { username: string, aclId: string }; + +export class User extends ScryptedDeviceBase implements Settings, ScryptedUser { + async getScryptedUserAccessControl(): Promise { + return { + devicesAccessControls: [ + addAccessControlsForInterface(sdk.systemManager.getDeviceByName('@scrypted/core').id, + ScryptedInterface.ScryptedDevice, + ScryptedInterface.EngineIOHandler), + ] + }; + } + + async getSettings(): Promise { + return [ + { + key: 'password', + title: 'Password', + type: 'password', + } + ] + } + + async putSetting(key: string, value: SettingValue): Promise { + if (key !== 'password') + return; + const usersService = await sdk.systemManager.getComponent('users'); + const users: DBUser[] = await usersService.getAllUsers(); + const user = users.find(user => user.username === this.nativeId.substring('user:'.length)); + if (!user) + return; + await usersService.addUser(user.username, value.toString(), user.aclId); + } +} + +export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvider, DeviceCreator { + constructor() { + super(UsersNativeId); + + this.syncUsers(); + } + + async getDevice(nativeId: string): Promise { + return new User(nativeId); + } + + async releaseDevice(id: string, nativeId: string): Promise { + const username = nativeId.substring('user:'.length); + const usersService = await sdk.systemManager.getComponent('users'); + await usersService.removeUser(username); + } + + async getCreateDeviceSettings(): Promise { + return [ + { + key: 'username', + title: 'User name', + }, + { + key: 'admin', + type: 'boolean', + title: 'Admin', + description: 'Grant this user administrator privileges.', + }, + { + key: 'password', + type: 'password', + title: 'Password', + } + ] + } + + async createDevice(settings: DeviceCreatorSettings): Promise { + const { username, password, admin } = settings; + const usersService = await sdk.systemManager.getComponent('users'); + const nativeId = `user:${username}`; + const aclId = await sdk.deviceManager.onDeviceDiscovered({ + providerNativeId: this.nativeId, + name: username.toString(), + nativeId, + interfaces: [ + ScryptedInterface.ScryptedUser, + ScryptedInterface.Settings, + ], + type: ScryptedDeviceType.Builtin, + }) + + await usersService.addUser(username, password, admin ? undefined : aclId); + await this.syncUsers(); + return nativeId; + } + + async getReadmeMarkdown(): Promise { + return "Create and Manage the users that can log into Scrypted."; + } + + async syncUsers() { + const usersService = await sdk.systemManager.getComponent('users'); + const users: DBUser[] = await usersService.getAllUsers(); + await sdk.deviceManager.onDevicesChanged({ + providerNativeId: this.nativeId, + devices: users.map(user => ({ + name: user.username, + nativeId: `user:${user.username}`, + interfaces: [ + ScryptedInterface.ScryptedUser, + ScryptedInterface.Settings, + ], + type: ScryptedDeviceType.Builtin, + })), + }) + } +} diff --git a/plugins/core/ui/src/components/AggregateComponent.vue b/plugins/core/ui/src/components/AggregateComponent.vue index 8c3047059..619876f4b 100644 --- a/plugins/core/ui/src/components/AggregateComponent.vue +++ b/plugins/core/ui/src/components/AggregateComponent.vue @@ -1,5 +1,5 @@