mirror of
https://github.com/koush/scrypted.git
synced 2026-02-14 02:42:06 +00:00
core: add support for users and acls
This commit is contained in:
@@ -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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Setting[]> {
|
||||
@@ -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<void> {
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
}
|
||||
|
||||
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<boolean> {
|
||||
// only allow admin users to access these services.
|
||||
if (request.aclId)
|
||||
return false;
|
||||
const check = this.checkEngineIoEndpoint(request, name);
|
||||
if (!check)
|
||||
return false;
|
||||
|
||||
@@ -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<void> {
|
||||
}
|
||||
}
|
||||
@@ -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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
117
plugins/core/src/user.ts
Normal file
117
plugins/core/src/user.ts
Normal file
@@ -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<ScryptedUserAccessControl> {
|
||||
return {
|
||||
devicesAccessControls: [
|
||||
addAccessControlsForInterface(sdk.systemManager.getDeviceByName('@scrypted/core').id,
|
||||
ScryptedInterface.ScryptedDevice,
|
||||
ScryptedInterface.EngineIOHandler),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'password',
|
||||
title: 'Password',
|
||||
type: 'password',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: SettingValue): Promise<void> {
|
||||
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<any> {
|
||||
return new User(nativeId);
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
const username = nativeId.substring('user:'.length);
|
||||
const usersService = await sdk.systemManager.getComponent('users');
|
||||
await usersService.removeUser(username);
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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,
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Device v-if="id" :id="id"></Device>
|
||||
<Device v-if="id" :deviceId="id"></Device>
|
||||
</template>
|
||||
<script>
|
||||
import Device from "./Device.vue";
|
||||
|
||||
@@ -496,7 +496,7 @@ export default {
|
||||
versions: null,
|
||||
};
|
||||
const device = this.device;
|
||||
pluginData.nativeId = await plugins.getNativeId(this.id);
|
||||
pluginData.nativeId = device.nativeId;
|
||||
pluginData.storage = await plugins.getStorage(this.id);
|
||||
pluginData.pluginId = this.device.pluginId;
|
||||
pluginData.packageJson = await plugins.getPackageJson(this.device.pluginId);
|
||||
@@ -571,7 +571,7 @@ export default {
|
||||
return this.$store.state.scrypted.devices;
|
||||
},
|
||||
id() {
|
||||
return this.$route.params.id || this.$props.id;
|
||||
return this.$route.params.id || this.$props.deviceId;
|
||||
},
|
||||
canLoad() {
|
||||
return this.devices.includes(this.id);
|
||||
@@ -583,7 +583,7 @@ export default {
|
||||
return this.$scrypted.systemManager.getDeviceById(this.id);
|
||||
},
|
||||
},
|
||||
props: ['id'],
|
||||
props: ['deviceId'],
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Device v-if="id" :id="id"></Device>
|
||||
<Device v-if="id" :deviceId="id"></Device>
|
||||
</template>
|
||||
<script>
|
||||
import Device from "./Device.vue";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Device v-if="id" :id="id"></Device>
|
||||
<Device v-if="id" :deviceId="id"></Device>
|
||||
</template>
|
||||
<script>
|
||||
import Device from "../Device.vue";
|
||||
|
||||
@@ -37,7 +37,6 @@ import EventListener from "../../interfaces/EventListener.vue";
|
||||
import OnOff from "../../interfaces/OnOff.vue";
|
||||
import Lock from "../../interfaces/Lock.vue";
|
||||
import Notifier from "../../interfaces/Notifier.vue";
|
||||
import SoftwareUpdate from "../../interfaces/SoftwareUpdate.vue";
|
||||
import ColorSettingHsv from "../../interfaces/ColorSettingHsv.vue";
|
||||
import StartStop from "../../interfaces/StartStop.vue";
|
||||
import Dock from "../../interfaces/Dock.vue";
|
||||
@@ -81,7 +80,6 @@ export default {
|
||||
Brightness,
|
||||
Lock,
|
||||
Notifier,
|
||||
SoftwareUpdate,
|
||||
ColorSettingHsv,
|
||||
ColorSettingRgb,
|
||||
ColorSettingTemperature,
|
||||
|
||||
@@ -12,7 +12,6 @@ export const actionableInterfaces = [
|
||||
ScryptedInterface.Notifier,
|
||||
ScryptedInterface.Pause,
|
||||
ScryptedInterface.Scene,
|
||||
ScryptedInterface.SoftwareUpdate,
|
||||
ScryptedInterface.StartStop,
|
||||
|
||||
// 'Timer',
|
||||
|
||||
@@ -4,7 +4,7 @@ import PluginUpdate from "./PluginUpdate.vue";
|
||||
import PluginPid from "./PluginPid.vue";
|
||||
import PluginStats from "./PluginStats.vue";
|
||||
import { getDeviceViewPath } from "../helpers";
|
||||
import { snapshotCurrentPlugins } from "./plugin";
|
||||
import { snapshotCurrentPlugins, getIdForNativeId } from "./plugin";
|
||||
|
||||
export default {
|
||||
mixins: [BasicComponent],
|
||||
@@ -16,13 +16,7 @@ export default {
|
||||
return `https://www.npmjs.com/package/${device.pluginId}`;
|
||||
},
|
||||
async openAutoupdater() {
|
||||
const plugins = await this.$scrypted.systemManager.getComponent(
|
||||
"plugins"
|
||||
);
|
||||
const id = await plugins.getIdForNativeId(
|
||||
"@scrypted/core",
|
||||
"automation:update-plugins"
|
||||
);
|
||||
const id = getIdForNativeId(systemManager, '@scrypted/core', 'scriptcore');
|
||||
this.$router.push(getDeviceViewPath(id));
|
||||
},
|
||||
async snapshotCurrentPlugins() {
|
||||
@@ -84,17 +78,22 @@ export default {
|
||||
const ids = Object.keys(this.$store.state.systemState);
|
||||
|
||||
const devices = [];
|
||||
const plugins = await this.$scrypted.systemManager.getComponent(
|
||||
"plugins"
|
||||
);
|
||||
const promises = ids.map(async (id) => {
|
||||
const device = this.$scrypted.systemManager.getDeviceById(id);
|
||||
if (device.id !== device.providerId) return;
|
||||
const { name, type } = device;
|
||||
const pluginId = device.pluginId;
|
||||
const pluginInfo = await plugins.getPluginInfo(pluginId);
|
||||
const { packageJson, pid, stats, rpcObjects, pendingResults } = pluginInfo;
|
||||
const npmPackageVersion = packageJson.version;
|
||||
let pluginInfo;
|
||||
try {
|
||||
const plugins = await this.$scrypted.systemManager.getComponent(
|
||||
"plugins"
|
||||
);
|
||||
pluginInfo = await plugins.getPluginInfo(pluginId);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
const { packageJson, pid, stats, rpcObjects, pendingResults } = pluginInfo || {};
|
||||
const npmPackageVersion = packageJson?.version;
|
||||
devices.push({
|
||||
id,
|
||||
name,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Scriptable } from '@scrypted/types';
|
||||
import { SystemManager } from '@scrypted/types';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import semver from 'semver';
|
||||
import { getAllDevices } from '../../common/mixin';
|
||||
const pluginSnapshot = require("!!raw-loader!./plugin-snapshot.ts").default.split('\n')
|
||||
.filter(line => !line.includes('SCRYPTED_FILTER_EXAMPLE_LINE'))
|
||||
.join('\n')
|
||||
@@ -74,11 +75,15 @@ export function getNpmPath(npmPackage: string) {
|
||||
return `https://www.npmjs.com/package/${npmPackage}`;
|
||||
}
|
||||
|
||||
export function getIdForNativeId(systemManager: SystemManager, pluginId: string, nativeId: string) {
|
||||
const found = getAllDevices(systemManager).find(device => device.pluginId === pluginId && device, nativeId === nativeId);
|
||||
return found?.id;
|
||||
}
|
||||
|
||||
export async function snapshotCurrentPlugins(scrypted: ScryptedStatic): Promise<string> {
|
||||
const { systemManager, deviceManager } = scrypted;
|
||||
|
||||
const plugins = await systemManager.getComponent("plugins");
|
||||
const id = await plugins.getIdForNativeId('@scrypted/core', 'scriptcore');
|
||||
const id = getIdForNativeId(systemManager, '@scrypted/core', 'scriptcore');
|
||||
const scriptCore = systemManager.getDeviceById<DeviceCreator & Scriptable>(id);
|
||||
const backupId = await scriptCore.createDevice({
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user