mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Add Clusterio Plugins
This commit is contained in:
282
exp_groups/controller.ts
Normal file
282
exp_groups/controller.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller";
|
||||
|
||||
import {
|
||||
PermissionStrings, PermissionStringsUpdate,
|
||||
PermissionGroup, PermissionGroupUpdate,
|
||||
InstancePermissionGroups,
|
||||
PermissionInstanceId,
|
||||
PermissionGroupEditEvent,
|
||||
} from "./messages";
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
static permissionGroupsPath = "exp_groups.json";
|
||||
static userGroupsPath = "exp_user_groups.json";
|
||||
|
||||
userToGroup: Map<lib.User["id"], PermissionGroup> = new Map(); // TODO this needs to be per instance
|
||||
permissionStrings!: Map<PermissionStrings["id"], PermissionStrings>;
|
||||
permissionGroups!: Map<InstancePermissionGroups["id"], InstancePermissionGroups>;
|
||||
|
||||
async init() {
|
||||
this.controller.handle(PermissionStringsUpdate, this.handlePermissionStringsUpdate.bind(this));
|
||||
this.controller.handle(PermissionGroupUpdate, this.handlePermissionGroupUpdate.bind(this));
|
||||
this.controller.handle(PermissionGroupEditEvent, this.handlePermissionGroupEditEvent.bind(this));
|
||||
this.controller.subscriptions.handle(PermissionStringsUpdate, this.handlePermissionStringsSubscription.bind(this));
|
||||
this.controller.subscriptions.handle(PermissionGroupUpdate, this.handlePermissionGroupSubscription.bind(this));
|
||||
this.controller.subscriptions.handle(PermissionGroupEditEvent);
|
||||
this.permissionStrings = new Map([["Global", new PermissionStrings("Global", new Set())]]);
|
||||
this.permissionGroups = new Map([["Global", new InstancePermissionGroups("Global")]]);
|
||||
await this.loadData();
|
||||
|
||||
// Add the default group if missing and add any missing cluster roles
|
||||
const clusterRoles = [...this.controller.userManager.roles.values()]
|
||||
for (const instanceGroups of this.permissionGroups.values()) {
|
||||
const groups = instanceGroups.groups;
|
||||
const instanceRoles = [...groups.values()].flatMap(group => [...group.roleIds.values()]);
|
||||
const missingRoles = clusterRoles.filter(role => instanceRoles.includes(role.id));
|
||||
const defaultGroup = groups.get("Default");
|
||||
if (defaultGroup) {
|
||||
for (const role of missingRoles) {
|
||||
defaultGroup.roleIds.add(role.id)
|
||||
}
|
||||
} else {
|
||||
groups.set("Default", new PermissionGroup(
|
||||
instanceGroups.instanceId,
|
||||
"Default",
|
||||
groups.size,
|
||||
new Set(missingRoles.map(role => role.id))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
||||
if (field === "exp_groups.allow_role_inconsistency") {
|
||||
// Do something with this.userToGroup
|
||||
}
|
||||
}
|
||||
|
||||
async onInstanceConfigFieldChanged(instance: InstanceInfo, field: string, curr: unknown, prev: unknown) {
|
||||
this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`);
|
||||
if (field === "exp_groups.sync_permission_groups") {
|
||||
const updates = []
|
||||
const now = Date.now();
|
||||
if (curr) {
|
||||
// Global sync enabled, we dont need the instance config
|
||||
const instanceGroups = this.permissionGroups.get(instance.id);
|
||||
if (instanceGroups) {
|
||||
this.permissionGroups.delete(instance.id);
|
||||
for (const group of instanceGroups.groups.values()) {
|
||||
group.updatedAtMs = now;
|
||||
group.isDeleted = true;
|
||||
updates.push(group);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Global sync disabled, make a copy of the global config as a base
|
||||
const global = this.permissionGroups.get("Global")!;
|
||||
const oldInstanceGroups = this.permissionGroups.get(instance.id);
|
||||
const instanceGroups = new InstancePermissionGroups(
|
||||
instance.id, new Map([...global.groups.values()].map(group => [group.name, group.copy(instance.id)]))
|
||||
)
|
||||
this.permissionGroups.set(instance.id, instanceGroups);
|
||||
for (const group of instanceGroups.groups.values()) {
|
||||
group.updatedAtMs = now;
|
||||
updates.push(group);
|
||||
}
|
||||
// If it has an old config (unexpected) then deal with it
|
||||
if (oldInstanceGroups) {
|
||||
for (const group of oldInstanceGroups.groups.values()) {
|
||||
if (!instanceGroups.groups.has(group.name)) {
|
||||
group.updatedAtMs = now;
|
||||
group.isDeleted = true;
|
||||
updates.push(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send the updates to all instances and controls
|
||||
if (updates.length) {
|
||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate(updates));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadPermissionGroups() {
|
||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.permissionGroupsPath);
|
||||
this.logger.verbose(`Loading ${file}`);
|
||||
try {
|
||||
const content = await fs.readFile(file, { encoding: "utf8" });
|
||||
for (const groupRaw of JSON.parse(content)) {
|
||||
const group = PermissionGroup.fromJSON(groupRaw);
|
||||
const instanceGroups = this.permissionGroups.get(group.instanceId);
|
||||
if (instanceGroups) {
|
||||
instanceGroups.groups.set(group.name, group);
|
||||
} else {
|
||||
this.permissionGroups.set(group.instanceId,
|
||||
new InstancePermissionGroups(group.instanceId, new Map([[group.name, group]]))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
this.logger.verbose("Creating new permission group database");
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async savePermissionGroups() {
|
||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.permissionGroupsPath);
|
||||
this.logger.verbose(`Writing ${file}`);
|
||||
await lib.safeOutputFile(file, JSON.stringify(
|
||||
[...this.permissionGroups.values()].flatMap(instanceGroups => [...instanceGroups.groups.values()])
|
||||
));
|
||||
}
|
||||
|
||||
async loadUserGroups() {
|
||||
if (!this.controller.config.get("exp_groups.allow_role_inconsistency")) return;
|
||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.userGroupsPath);
|
||||
this.logger.verbose(`Loading ${file}`);
|
||||
try {
|
||||
const content = await fs.readFile(file, { encoding: "utf8" });
|
||||
this.userToGroup = new Map(JSON.parse(content));
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") {
|
||||
this.logger.verbose("Creating new user group database");
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async saveUserGroups() {
|
||||
if (!this.controller.config.get("exp_groups.allow_role_inconsistency")) return;
|
||||
const file = path.resolve(this.controller.config.get("controller.database_directory"), ControllerPlugin.userGroupsPath);
|
||||
this.logger.verbose(`Writing ${file}`);
|
||||
await lib.safeOutputFile(file, JSON.stringify([...this.permissionGroups.entries()]));
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
await Promise.all([
|
||||
this.loadPermissionGroups(),
|
||||
this.loadUserGroups(),
|
||||
])
|
||||
}
|
||||
|
||||
async onSaveData() {
|
||||
await Promise.all([
|
||||
this.savePermissionGroups(),
|
||||
this.saveUserGroups(),
|
||||
])
|
||||
}
|
||||
|
||||
addPermisisonGroup(instanceId: PermissionInstanceId, name: string, permissions = new Set<string>(), silent = false) {
|
||||
const instanceGroups = this.permissionGroups.get(instanceId);
|
||||
if (!instanceGroups) {
|
||||
throw new Error("Instance ID does not exist");
|
||||
}
|
||||
if (instanceGroups.groups.has(name)) {
|
||||
return instanceGroups.groups.get(name)!;
|
||||
}
|
||||
for (const group of instanceGroups.groups.values()) {
|
||||
group.order += 1;
|
||||
}
|
||||
const group = new PermissionGroup(instanceId, name, 0, new Set(), permissions, Date.now(), false);
|
||||
instanceGroups.groups.set(group.id, group);
|
||||
if (!silent) {
|
||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate([group]));
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
removePermissionGroup(instanceId: PermissionInstanceId, name: string, silent = false) {
|
||||
const instanceGroups = this.permissionGroups.get(instanceId);
|
||||
if (!instanceGroups) {
|
||||
throw new Error("Instance ID does not exist");
|
||||
}
|
||||
const group = instanceGroups.groups.get(name)
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
for (const nextGroup of instanceGroups.groups.values()) {
|
||||
if (nextGroup.order > group.order) {
|
||||
nextGroup.order -= 1;
|
||||
}
|
||||
}
|
||||
instanceGroups.groups.delete(group.id);
|
||||
group.updatedAtMs = Date.now();
|
||||
group.isDeleted = true;
|
||||
if (!silent) {
|
||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate([group]));
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
async handlePermissionGroupEditEvent(event: PermissionGroupEditEvent) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async handlePermissionStringsUpdate(event: PermissionStringsUpdate) {
|
||||
for (const update of event.updates) {
|
||||
const global = this.permissionStrings.get("Global")!
|
||||
this.permissionStrings.set(update.instanceId as number, update)
|
||||
global.updatedAtMs = Math.max(global.updatedAtMs, update.updatedAtMs)
|
||||
for (const permission of update.permissions) {
|
||||
global.permissions.add(permission)
|
||||
}
|
||||
// TODO maybe check if changes have happened rather than always pushing updates
|
||||
this.controller.subscriptions.broadcast(new PermissionStringsUpdate([global, update]))
|
||||
}
|
||||
}
|
||||
|
||||
async handlePermissionGroupUpdate(event: PermissionGroupUpdate) {
|
||||
const updates = [];
|
||||
for (const group of event.updates) {
|
||||
const groups = this.permissionGroups.get(group.instanceId);
|
||||
if (!groups) continue;
|
||||
const existingGroup = groups.groups.get(group.id);
|
||||
let update
|
||||
if (!existingGroup) {
|
||||
update = this.addPermisisonGroup(group.instanceId, group.name, group.permissions, true);
|
||||
} else if (group.isDeleted) {
|
||||
update = this.removePermissionGroup(group.instanceId, group.name, true);
|
||||
} else {
|
||||
existingGroup.permissions = group.permissions;
|
||||
existingGroup.updatedAtMs = Date.now();
|
||||
update = existingGroup;
|
||||
}
|
||||
if (update) updates.push(update);
|
||||
}
|
||||
this.controller.subscriptions.broadcast(new PermissionGroupUpdate(updates));
|
||||
}
|
||||
|
||||
async handlePermissionStringsSubscription(request: lib.SubscriptionRequest, src: lib.Address) {
|
||||
const updates = [ ...this.permissionStrings.values() ]
|
||||
.filter(
|
||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
||||
)
|
||||
return updates.length ? new PermissionStringsUpdate(updates) : null;
|
||||
}
|
||||
|
||||
async handlePermissionGroupSubscription(request: lib.SubscriptionRequest, src: lib.Address) {
|
||||
const updates = [ ...this.permissionGroups.values() ]
|
||||
.flatMap(instanceGroups => [...instanceGroups.groups.values()])
|
||||
.filter(
|
||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
||||
)
|
||||
if (src.type === lib.Address.instance) {
|
||||
const instanceUpdates = updates.filter(group => group.instanceId === src.id || group.instanceId === "Global");
|
||||
this.logger.info(JSON.stringify(updates))
|
||||
this.logger.info(JSON.stringify(instanceUpdates))
|
||||
return instanceUpdates.length ? new PermissionGroupUpdate(instanceUpdates) : null;
|
||||
}
|
||||
return updates.length ? new PermissionGroupUpdate(updates) : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user