810 lines
30 KiB
TypeScript
810 lines
30 KiB
TypeScript
import * as log from "../log";
|
|
import {LogCategory, logDebug, logInfo, logTrace, LogType, logWarn} from "../log";
|
|
import {PermissionType} from "../permission/PermissionType";
|
|
import {LaterPromise} from "../utils/LaterPromise";
|
|
import {ServerCommand} from "../connection/ConnectionBase";
|
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
|
import {ConnectionHandler} from "../ConnectionHandler";
|
|
import {AbstractCommandHandler} from "../connection/AbstractCommandHandler";
|
|
import {Registry} from "../events";
|
|
import {tr} from "../i18n/localize";
|
|
import {ErrorCode} from "../connection/ErrorCode";
|
|
|
|
export class PermissionInfo {
|
|
name: string;
|
|
id: number;
|
|
description: string;
|
|
|
|
is_boolean() { return this.name.startsWith("b_"); }
|
|
id_grant() : number {
|
|
return this.id | (1 << 15);
|
|
}
|
|
}
|
|
|
|
export class PermissionGroup {
|
|
begin: number;
|
|
end: number;
|
|
deep: number;
|
|
name: string;
|
|
}
|
|
|
|
export class GroupedPermissions {
|
|
group: PermissionGroup;
|
|
permissions: PermissionInfo[];
|
|
children: GroupedPermissions[];
|
|
parent: GroupedPermissions;
|
|
}
|
|
|
|
export class PermissionValue {
|
|
readonly type: PermissionInfo;
|
|
value: number | undefined; /* undefined if no permission is given */
|
|
flag_skip: boolean;
|
|
flag_negate: boolean;
|
|
granted_value: number;
|
|
|
|
constructor(type, value?) {
|
|
this.type = type;
|
|
this.value = value;
|
|
}
|
|
|
|
granted(requiredValue: number, required: boolean = true) : boolean {
|
|
let result;
|
|
result = this.value == -1 || this.value >= requiredValue || (this.value == -2 && requiredValue == -2 && !required);
|
|
|
|
logTrace(LogCategory.PERMISSIONS,
|
|
tr("Required permission test resulted for permission %s: %s. Required value: %s, Granted value: %s"),
|
|
this.type ? this.type.name : "unknown",
|
|
result ? tr("granted") : tr("denied"),
|
|
requiredValue + (required ? " (" + tr("required") + ")" : ""),
|
|
this.hasValue() ? this.value : tr("none")
|
|
);
|
|
return result;
|
|
}
|
|
|
|
hasValue() : boolean {
|
|
return typeof(this.value) !== "undefined" && this.value != -2;
|
|
}
|
|
|
|
hasGrant() : boolean {
|
|
return typeof(this.granted_value) !== "undefined" && this.granted_value != -2;
|
|
}
|
|
|
|
valueOr(fallback: number) {
|
|
return this.hasValue() ? this.value : fallback;
|
|
}
|
|
|
|
valueNormalOr(fallback: number) {
|
|
if(this.hasValue()) {
|
|
if(this.value === -1) {
|
|
return Number.MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
return this.value;
|
|
} else {
|
|
return fallback;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class NeededPermissionValue extends PermissionValue {
|
|
constructor(type, value) {
|
|
super(type, value);
|
|
}
|
|
}
|
|
|
|
export type PermissionRequestKeys = {
|
|
client_id?: number;
|
|
channel_id?: number;
|
|
playlist_id?: number;
|
|
}
|
|
|
|
export type PermissionRequest = PermissionRequestKeys & {
|
|
timeout_id: any;
|
|
promise: LaterPromise<PermissionValue[]>;
|
|
};
|
|
|
|
export namespace find {
|
|
export type Entry = {
|
|
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
|
|
value: number;
|
|
id: number;
|
|
}
|
|
|
|
export type Client = Entry & {
|
|
type: "client",
|
|
|
|
client_id: number;
|
|
}
|
|
|
|
export type Channel = Entry & {
|
|
type: "channel",
|
|
|
|
channel_id: number;
|
|
}
|
|
|
|
export type Server = Entry & {
|
|
type: "server"
|
|
}
|
|
|
|
export type ClientChannel = Entry & {
|
|
type: "client_channel",
|
|
|
|
client_id: number;
|
|
channel_id: number;
|
|
}
|
|
|
|
export type ChannelGroup = Entry & {
|
|
type: "channel_group",
|
|
|
|
group_id: number;
|
|
}
|
|
|
|
export type ServerGroup = Entry & {
|
|
type: "server_group",
|
|
|
|
group_id: number;
|
|
}
|
|
}
|
|
|
|
export interface PermissionManagerEvents {
|
|
client_permissions_changed: {}
|
|
}
|
|
|
|
export type RequestLists =
|
|
"requests_channel_permissions" |
|
|
"requests_client_permissions" |
|
|
"requests_client_channel_permissions" |
|
|
"requests_playlist_permissions" |
|
|
"requests_playlist_client_permissions";
|
|
|
|
export class PermissionManager extends AbstractCommandHandler {
|
|
readonly events = new Registry<PermissionManagerEvents>();
|
|
|
|
readonly handle: ConnectionHandler;
|
|
|
|
permissionList: PermissionInfo[] = [];
|
|
permissionGroups: PermissionGroup[] = [];
|
|
neededPermissions: NeededPermissionValue[] = [];
|
|
|
|
needed_permission_change_listener: {[permission: string]:((value?: PermissionValue) => void)[]} = {};
|
|
|
|
requests_channel_permissions: PermissionRequest[] = [];
|
|
requests_client_permissions: PermissionRequest[] = [];
|
|
requests_client_channel_permissions: PermissionRequest[] = [];
|
|
requests_playlist_permissions: PermissionRequest[] = [];
|
|
requests_playlist_client_permissions: PermissionRequest[] = [];
|
|
|
|
requests_permfind: {
|
|
timeout_id: number,
|
|
permission: string,
|
|
callback: (status: "success" | "error", data: any) => void
|
|
}[] = [];
|
|
|
|
initializedListener: ((initialized: boolean) => void)[] = [];
|
|
private cacheNeededPermissions: any;
|
|
|
|
/* Static info mapping until TeaSpeak implements a detailed info */
|
|
static readonly group_mapping: {name: string, deep: number}[] = [
|
|
{name: tr("Global"), deep: 0},
|
|
{name: tr("Information"), deep: 1},
|
|
{name: tr("Virtual server management"), deep: 1},
|
|
{name: tr("Administration"), deep: 1},
|
|
{name: tr("Settings"), deep: 1},
|
|
{name: tr("Virtual Server"), deep: 0},
|
|
{name: tr("Information"), deep: 1},
|
|
{name: tr("Administration"), deep: 1},
|
|
{name: tr("Settings"), deep: 1},
|
|
{name: tr("Channel"), deep: 0},
|
|
{name: tr("Information"), deep: 1},
|
|
{name: tr("Create"), deep: 1},
|
|
{name: tr("Modify"), deep: 1},
|
|
{name: tr("Delete"), deep: 1},
|
|
{name: tr("Access"), deep: 1},
|
|
{name: tr("Group"), deep: 0},
|
|
{name: tr("Information"), deep: 1},
|
|
{name: tr("Create"), deep: 1},
|
|
{name: tr("Modify"), deep: 1},
|
|
{name: tr("Delete"), deep: 1},
|
|
{name: tr("Client"), deep: 0},
|
|
{name: tr("Information"), deep: 1},
|
|
{name: tr("Admin"), deep: 1},
|
|
{name: tr("Basics"), deep: 1},
|
|
{name: tr("Modify"), deep: 1},
|
|
//TODO Music bot
|
|
{name: tr("File Transfer"), deep: 0},
|
|
];
|
|
private _group_mapping;
|
|
|
|
public static parse_permission_bulk(json: any[], manager: PermissionManager) : PermissionValue[] {
|
|
let permissions: PermissionValue[] = [];
|
|
for(let perm of json) {
|
|
if(perm["permid"] === undefined) continue;
|
|
|
|
let perm_id = parseInt(perm["permid"]);
|
|
let perm_grant = (perm_id & (1 << 15)) > 0;
|
|
if(perm_grant)
|
|
perm_id &= ~(1 << 15);
|
|
|
|
let perm_info = manager.resolveInfo(perm_id);
|
|
if(!perm_info) {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Got unknown permission id (%o/%o (%o))!"), perm["permid"], perm_id, perm["permsid"]);
|
|
return;
|
|
}
|
|
|
|
let permission: PermissionValue;
|
|
for(let ref_perm of permissions) {
|
|
if(ref_perm.type == perm_info) {
|
|
permission = ref_perm;
|
|
break;
|
|
}
|
|
}
|
|
if(!permission) {
|
|
permission = new PermissionValue(perm_info, 0);
|
|
permission.granted_value = undefined;
|
|
permission.value = undefined;
|
|
permissions.push(permission);
|
|
}
|
|
if(perm_grant) {
|
|
permission.granted_value = parseInt(perm["permvalue"]);
|
|
} else {
|
|
permission.value = parseInt(perm["permvalue"]);
|
|
permission.flag_negate = perm["permnegated"] == "1";
|
|
permission.flag_skip = perm["permskip"] == "1";
|
|
}
|
|
}
|
|
return permissions;
|
|
}
|
|
|
|
constructor(client: ConnectionHandler) {
|
|
super(client.serverConnection);
|
|
|
|
//FIXME? Dont register the handler like this?
|
|
this.volatile_handler_boss = true;
|
|
client.serverConnection.getCommandHandler().registerHandler(this);
|
|
|
|
this.handle = client;
|
|
}
|
|
|
|
destroy() {
|
|
this.handle.serverConnection && this.handle.serverConnection.getCommandHandler().unregisterHandler(this);
|
|
this.needed_permission_change_listener = {};
|
|
|
|
this.permissionList = undefined;
|
|
this.permissionGroups = undefined;
|
|
|
|
this.neededPermissions = undefined;
|
|
|
|
/* delete all requests */
|
|
for(const key of Object.keys(this))
|
|
if(key.startsWith("requests"))
|
|
delete this[key];
|
|
|
|
this.initializedListener = undefined;
|
|
this.cacheNeededPermissions = undefined;
|
|
}
|
|
|
|
handle_command(command: ServerCommand): boolean {
|
|
switch (command.command) {
|
|
case "notifyclientneededpermissions":
|
|
this.onNeededPermissions(command.arguments);
|
|
return true;
|
|
case "notifypermissionlist":
|
|
this.onPermissionList(command.arguments);
|
|
return true;
|
|
case "notifychannelpermlist":
|
|
this.onChannelPermList(command.arguments);
|
|
return true;
|
|
case "notifyclientpermlist":
|
|
this.onClientPermList(command.arguments);
|
|
return true;
|
|
case "notifychannelclientpermlist":
|
|
this.onChannelClientPermList(command.arguments);
|
|
return true;
|
|
case "notifyplaylistpermlist":
|
|
this.onPlaylistPermList(command.arguments);
|
|
return true;
|
|
case "notifyplaylistclientpermlist":
|
|
this.onPlaylistClientPermList(command.arguments);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
initialized() : boolean {
|
|
return this.permissionList.length > 0;
|
|
}
|
|
|
|
public requestPermissionList() {
|
|
this.handle.serverConnection.send_command("permissionlist");
|
|
}
|
|
|
|
private onPermissionList(json) {
|
|
this.permissionList = [];
|
|
this.permissionGroups = [];
|
|
this._group_mapping = PermissionManager.group_mapping.slice();
|
|
|
|
let group = log.group(log.LogType.TRACE, LogCategory.PERMISSIONS, tr("Permission mapping"));
|
|
const table_entries = [];
|
|
let permission_id = 0;
|
|
for(let e of json) {
|
|
if(e["group_id_end"]) {
|
|
let group = new PermissionGroup();
|
|
group.begin = this.permissionGroups.length ? this.permissionGroups.last().end : 0;
|
|
group.end = parseInt(e["group_id_end"]);
|
|
group.deep = 0;
|
|
group.name = tr("Group ") + e["group_id_end"];
|
|
|
|
let info = this._group_mapping.pop_front();
|
|
if(info) {
|
|
group.name = info.name;
|
|
group.deep = info.deep;
|
|
}
|
|
this.permissionGroups.push(group);
|
|
continue;
|
|
}
|
|
|
|
let perm = new PermissionInfo();
|
|
permission_id++;
|
|
|
|
perm.name = e["permname"];
|
|
perm.id = parseInt(e["permid"]) || permission_id; /* using permission_id as fallback if we dont have permid */
|
|
perm.description = e["permdesc"];
|
|
this.permissionList.push(perm);
|
|
|
|
table_entries.push({
|
|
"id": perm.id,
|
|
"name": perm.name,
|
|
"description": perm.description
|
|
});
|
|
}
|
|
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Permission list", table_entries);
|
|
group.end();
|
|
|
|
logInfo(LogCategory.PERMISSIONS, tr("Got %i permissions"), this.permissionList.length);
|
|
if(this.cacheNeededPermissions) {
|
|
this.onNeededPermissions(this.cacheNeededPermissions);
|
|
}
|
|
|
|
for(let listener of this.initializedListener) {
|
|
listener(true);
|
|
}
|
|
}
|
|
|
|
private onNeededPermissions(json: any[]) {
|
|
if(this.permissionList.length == 0) {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Got needed permissions but don't have a permission list!"));
|
|
this.cacheNeededPermissions = json;
|
|
return;
|
|
}
|
|
this.cacheNeededPermissions = undefined;
|
|
|
|
let permissionsCopy = this.neededPermissions.slice();
|
|
let permissionAddCount = 0;
|
|
let permissionRemoveCount = 0;
|
|
|
|
let group = log.group(log.LogType.TRACE, LogCategory.PERMISSIONS, tr("Got %d needed permissions."), json.length);
|
|
const tableEntries = [];
|
|
|
|
for(let notifyEntry of json) {
|
|
let entry: NeededPermissionValue = undefined;
|
|
for(let permission of permissionsCopy) {
|
|
if(permission.type.id == notifyEntry["permid"]) {
|
|
entry = permission;
|
|
permissionsCopy.remove(permission);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const permissionValue = parseInt(notifyEntry["permvalue"]);
|
|
if(permissionValue === 0) {
|
|
if(entry) {
|
|
permissionRemoveCount++;
|
|
|
|
entry.value = -2;
|
|
for(const listener of this.needed_permission_change_listener[entry.type.name] || []) {
|
|
listener();
|
|
}
|
|
}
|
|
/*
|
|
* Permission hasn't been granted.
|
|
* TeamSpeak uses this as "permission removed".
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if(!entry) {
|
|
let info = this.resolveInfo(notifyEntry["permid"]);
|
|
if(info) {
|
|
entry = new NeededPermissionValue(info, -2);
|
|
this.neededPermissions.push(entry);
|
|
} else {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Could not resolve perm for id %s (%o|%o)"), notifyEntry["permid"], notifyEntry, info);
|
|
continue;
|
|
}
|
|
permissionAddCount++;
|
|
}
|
|
entry.value = permissionValue;
|
|
|
|
for(const listener of this.needed_permission_change_listener[entry.type.name] || []) {
|
|
listener();
|
|
}
|
|
|
|
tableEntries.push({
|
|
"permission": entry.type.name,
|
|
"value": entry.value
|
|
});
|
|
}
|
|
|
|
log.table(LogType.DEBUG, LogCategory.PERMISSIONS, "Needed client permissions", tableEntries);
|
|
group.end();
|
|
|
|
if(this.handle.serverConnection.getServerType() === "teamspeak" || json[0]["relative"] === "1") {
|
|
/* We don't update the full list every time. Instead we're only propagating changes. */
|
|
} else {
|
|
permissionRemoveCount = permissionsCopy.length;
|
|
for(let entry of permissionsCopy) {
|
|
entry.value = -2;
|
|
for(const listener of this.needed_permission_change_listener[entry.type.name] || []) {
|
|
listener();
|
|
}
|
|
}
|
|
}
|
|
logDebug(LogCategory.PERMISSIONS, tr("Dropping %o needed permissions and added %o permissions."), permissionRemoveCount, permissionAddCount);
|
|
|
|
this.events.fire("client_permissions_changed");
|
|
}
|
|
|
|
register_needed_permission(key: PermissionType, listener: () => any) : () => void {
|
|
const array = this.needed_permission_change_listener[key] || [];
|
|
array.push(listener);
|
|
this.needed_permission_change_listener[key] = array;
|
|
|
|
return () => this.needed_permission_change_listener[key]?.remove(listener);
|
|
}
|
|
|
|
unregister_needed_permission(key: PermissionType, listener: () => any) {
|
|
const array = this.needed_permission_change_listener[key];
|
|
if(!array) return;
|
|
|
|
array.remove(listener);
|
|
this.needed_permission_change_listener[key] = array.length > 0 ? array : undefined;
|
|
}
|
|
|
|
resolveInfo?(key: number | string | PermissionType) : PermissionInfo {
|
|
for(let perm of this.permissionList)
|
|
if(perm.id == key || perm.name == key)
|
|
return perm;
|
|
return undefined;
|
|
}
|
|
|
|
/* channel permission request */
|
|
private onChannelPermList(json) {
|
|
let channelId: number = parseInt(json[0]["cid"]);
|
|
|
|
this.fullfill_permission_request("requests_channel_permissions", {
|
|
channel_id: channelId
|
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
|
}
|
|
|
|
private execute_channel_permission_request(request: PermissionRequestKeys, processResult?: boolean) {
|
|
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}, { process_result: !!processResult }).catch(error => {
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT)
|
|
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
|
|
else
|
|
this.fullfill_permission_request("requests_channel_permissions", request, "error", error);
|
|
});
|
|
}
|
|
|
|
requestChannelPermissions(channelId: number, processResult?: boolean) : Promise<PermissionValue[]> {
|
|
const keys: PermissionRequestKeys = {
|
|
channel_id: channelId
|
|
};
|
|
return this.execute_permission_request("requests_channel_permissions", keys, criteria => this.execute_channel_permission_request(criteria, processResult));
|
|
}
|
|
|
|
/* client permission request */
|
|
private onClientPermList(json: any[]) {
|
|
let client = parseInt(json[0]["cldbid"]);
|
|
this.fullfill_permission_request("requests_client_permissions", {
|
|
client_id: client
|
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
|
}
|
|
|
|
private execute_client_permission_request(request: PermissionRequestKeys) {
|
|
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT)
|
|
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
|
|
else
|
|
this.fullfill_permission_request("requests_client_permissions", request, "error", error);
|
|
});
|
|
}
|
|
|
|
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
|
const keys: PermissionRequestKeys = {
|
|
client_id: client_id
|
|
};
|
|
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
|
|
}
|
|
|
|
/* client channel permission request */
|
|
private onChannelClientPermList(json: any[]) {
|
|
let client_id = parseInt(json[0]["cldbid"]);
|
|
let channel_id = parseInt(json[0]["cid"]);
|
|
|
|
this.fullfill_permission_request("requests_client_channel_permissions", {
|
|
client_id: client_id,
|
|
channel_id: channel_id
|
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
|
}
|
|
|
|
private execute_client_channel_permission_request(request: PermissionRequestKeys) {
|
|
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
|
|
.catch(error => {
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT)
|
|
this.fullfill_permission_request("requests_client_channel_permissions", request, "success", []);
|
|
else
|
|
this.fullfill_permission_request("requests_client_channel_permissions", request, "error", error);
|
|
});
|
|
}
|
|
|
|
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
|
const keys: PermissionRequestKeys = {
|
|
client_id: client_id,
|
|
channel_id: channel_id
|
|
};
|
|
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
|
|
}
|
|
|
|
/* playlist permissions */
|
|
private onPlaylistPermList(json: any[]) {
|
|
let playlist_id = parseInt(json[0]["playlist_id"]);
|
|
|
|
this.fullfill_permission_request("requests_playlist_permissions", {
|
|
playlist_id: playlist_id
|
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
|
}
|
|
|
|
private execute_playlist_permission_request(request: PermissionRequestKeys) {
|
|
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
|
|
.catch(error => {
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT)
|
|
this.fullfill_permission_request("requests_playlist_permissions", request, "success", []);
|
|
else
|
|
this.fullfill_permission_request("requests_playlist_permissions", request, "error", error);
|
|
});
|
|
}
|
|
|
|
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
|
const keys: PermissionRequestKeys = {
|
|
playlist_id: playlist_id
|
|
};
|
|
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
|
|
}
|
|
|
|
/* playlist client permissions */
|
|
private onPlaylistClientPermList(json: any[]) {
|
|
let playlist_id = parseInt(json[0]["playlist_id"]);
|
|
let client_id = parseInt(json[0]["cldbid"]);
|
|
|
|
this.fullfill_permission_request("requests_playlist_client_permissions", {
|
|
playlist_id: playlist_id,
|
|
client_id: client_id
|
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
|
}
|
|
|
|
private execute_playlist_client_permission_request(request: PermissionRequestKeys) {
|
|
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
|
|
.catch(error => {
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT)
|
|
this.fullfill_permission_request("requests_playlist_client_permissions", request, "success", []);
|
|
else
|
|
this.fullfill_permission_request("requests_playlist_client_permissions", request, "error", error);
|
|
});
|
|
}
|
|
|
|
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
|
|
const keys: PermissionRequestKeys = {
|
|
playlist_id: playlist_id,
|
|
client_id: client_database_id
|
|
};
|
|
return this.execute_permission_request("requests_playlist_client_permissions", keys, this.execute_playlist_client_permission_request.bind(this));
|
|
}
|
|
|
|
private readonly criteria_equal = (a, b) => {
|
|
for(const criteria of ["client_id", "channel_id", "playlist_id"]) {
|
|
if((typeof a[criteria] === "undefined") !== (typeof b[criteria] === "undefined")) return false;
|
|
if(a[criteria] != b[criteria]) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
private execute_permission_request(list: RequestLists,
|
|
criteria: PermissionRequestKeys,
|
|
execute: (criteria: PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
|
|
for(const request of this[list])
|
|
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
|
|
return request.promise;
|
|
|
|
const result = Object.assign({
|
|
timeout_id: setTimeout(() => this.fullfill_permission_request(list, criteria, "error", tr("timeout")), 5000),
|
|
promise: new LaterPromise<PermissionValue[]>()
|
|
}, criteria);
|
|
this[list].push(result);
|
|
execute(criteria);
|
|
return result.promise;
|
|
};
|
|
|
|
private fullfill_permission_request(list: RequestLists, criteria: PermissionRequestKeys, status: "success" | "error", result: any) {
|
|
for(const request of this[list]) {
|
|
if(this.criteria_equal(request, criteria)) {
|
|
this[list].remove(request);
|
|
clearTimeout(request.timeout_id);
|
|
status === "error" ? request.promise.rejected(result) : request.promise.resolved(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
find_permission(...permissions: string[]) : Promise<find.Entry[]> {
|
|
const permission_ids = [];
|
|
for(const permission of permissions) {
|
|
const info = this.resolveInfo(permission);
|
|
if(!info) continue;
|
|
|
|
permission_ids.push(info.id);
|
|
}
|
|
if(!permission_ids.length) return Promise.resolve([]);
|
|
|
|
return new Promise<find.Entry[]>((resolve, reject) => {
|
|
const single_handler = {
|
|
command: "notifypermfind",
|
|
function: command => {
|
|
const result: find.Entry[] = [];
|
|
for(const entry of command.arguments) {
|
|
const perm_id = parseInt(entry["p"]);
|
|
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
|
|
|
|
const value = parseInt(entry["v"]);
|
|
const type = parseInt(entry["t"]);
|
|
|
|
let data;
|
|
switch (type) {
|
|
case 0:
|
|
data = {
|
|
type: "server_group",
|
|
group_id: parseInt(entry["id1"]),
|
|
} as find.ServerGroup;
|
|
break;
|
|
case 1:
|
|
data = {
|
|
type: "client",
|
|
client_id: parseInt(entry["id2"]),
|
|
} as find.Client;
|
|
break;
|
|
case 2:
|
|
data = {
|
|
type: "channel",
|
|
channel_id: parseInt(entry["id2"]),
|
|
} as find.Channel;
|
|
break;
|
|
case 3:
|
|
data = {
|
|
type: "channel_group",
|
|
group_id: parseInt(entry["id1"]),
|
|
} as find.ChannelGroup;
|
|
break;
|
|
case 4:
|
|
data = {
|
|
type: "client_channel",
|
|
client_id: parseInt(entry["id1"]),
|
|
channel_id: parseInt(entry["id1"]),
|
|
} as find.ClientChannel;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
data.id = perm_id;
|
|
data.value = value;
|
|
result.push(data);
|
|
}
|
|
|
|
resolve(result);
|
|
return true;
|
|
}
|
|
};
|
|
this.handler_boss.registerSingleHandler(single_handler);
|
|
|
|
this.connection.send_command("permfind", permission_ids.map(e => { return {permid: e }})).catch(error => {
|
|
this.handler_boss.removeSingleHandler(single_handler);
|
|
|
|
if(error instanceof CommandResult && error.id == ErrorCode.DATABASE_EMPTY_RESULT) {
|
|
resolve([]);
|
|
return;
|
|
}
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
neededPermission(key: number | string | PermissionType | PermissionInfo) : NeededPermissionValue {
|
|
for(let perm of this.neededPermissions)
|
|
if(perm.type.id == key || perm.type.name == key || perm.type == key)
|
|
return perm;
|
|
|
|
logDebug(LogCategory.PERMISSIONS, tr("Could not resolve grant permission %o. Creating a new one."), key);
|
|
let info = key instanceof PermissionInfo ? key : this.resolveInfo(key);
|
|
if(!info) {
|
|
logWarn(LogCategory.PERMISSIONS, tr("Requested needed permission with invalid key! (%o)"), key);
|
|
return new NeededPermissionValue(undefined, -2);
|
|
}
|
|
let result = new NeededPermissionValue(info, -2);
|
|
this.neededPermissions.push(result);
|
|
return result;
|
|
}
|
|
|
|
groupedPermissions() : GroupedPermissions[] {
|
|
let result: GroupedPermissions[] = [];
|
|
let current: GroupedPermissions;
|
|
|
|
for(let group of this.permissionGroups) {
|
|
if(group.deep == 0) {
|
|
current = new GroupedPermissions();
|
|
current.group = group;
|
|
current.parent = undefined;
|
|
current.children = [];
|
|
current.permissions = [];
|
|
result.push(current);
|
|
} else {
|
|
if(!current) {
|
|
throw tr("invalid order!");
|
|
} else {
|
|
while(group.deep <= current.group.deep)
|
|
current = current.parent;
|
|
|
|
let parent = current;
|
|
current = new GroupedPermissions();
|
|
current.group = group;
|
|
current.parent = parent;
|
|
current.children = [];
|
|
current.permissions = [];
|
|
parent.children.push(current);
|
|
}
|
|
}
|
|
|
|
for(let permission of this.permissionList)
|
|
if(permission.id > current.group.begin && permission.id <= current.group.end)
|
|
current.permissions.push(permission);
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Generates an enum with all know permission types, used for the enum above
|
|
*/
|
|
export_permission_types() {
|
|
let result = "";
|
|
result = result + "enum PermissionType {\n";
|
|
|
|
for(const permission of this.permissionList) {
|
|
if(!permission.name) continue;
|
|
result = result + "\t" + permission.name.toUpperCase() + " = \"" + permission.name.toLowerCase() + "\", /* Permission ID: " + permission.id + " */\n";
|
|
}
|
|
|
|
result = result + "}";
|
|
return result;
|
|
}
|
|
|
|
getFailedPermission(command: CommandResult, index?: number) {
|
|
const json = command.bulks[typeof index === "number" ? index : 0] || {};
|
|
if("failed_permsid" in json) {
|
|
return json["failed_permsid"];
|
|
} else if("failed_permid" in json) {
|
|
const info = this.resolveInfo(parseInt(json["failed_permid"]));
|
|
return info ? info.name : "permission id " + json["failed_permid"];
|
|
} else {
|
|
return tr("unknown permission");
|
|
}
|
|
}
|
|
} |