- Replaced all direct `process.env` references with `Config` class constants. - Added `dotenv` package to manage environment variables. - Updated affected files, including schema loader, Zabbix API, resolvers, logging system, and integration points. - Improved maintainability and consistency in environment variable handling.
218 lines
9.8 KiB
TypeScript
218 lines
9.8 KiB
TypeScript
import {ParsedArgs, ZabbixErrorResult, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
|
|
import {ZabbixAPI} from "./zabbix-api.js";
|
|
import {InputMaybe, Permission, QueryHasPermissionsArgs, UserPermission} from "../schema/generated/graphql.js";
|
|
import {ApiErrorCode, PermissionNumber} from "../model/model_enum_values.js";
|
|
import {Config} from "../common_utils.js";
|
|
|
|
|
|
export class ZabbixRequestWithPermissions<T extends ZabbixResult, A extends ParsedArgs = ParsedArgs> extends ZabbixRequest<T, A> {
|
|
|
|
constructor(public path: string, public authToken?: string | null, public cookie?: string | null,
|
|
protected permissionsNeeded?: QueryHasPermissionsArgs) {
|
|
super(path, authToken, cookie);
|
|
}
|
|
async prepare(zabbixAPI: ZabbixAPI, _args?: A): Promise<T | ZabbixErrorResult | undefined> {
|
|
// If prepare returns something else than undefined, the execution will be skipped and the
|
|
// result returned
|
|
this.prepResult = await this.assureUserPermissions(zabbixAPI);
|
|
return this.prepResult;
|
|
}
|
|
async assureUserPermissions(zabbixAPI: ZabbixAPI) {
|
|
if (this.permissionsNeeded &&
|
|
!await ZabbixPermissionsHelper.hasUserPermissions(zabbixAPI, this.permissionsNeeded, this.authToken, this.cookie)) {
|
|
return {
|
|
error: {
|
|
message: "User does not have the required permissions",
|
|
code: ApiErrorCode.PERMISSION_ERROR,
|
|
path: this.path,
|
|
args: this.permissionsNeeded
|
|
}
|
|
}
|
|
} else {
|
|
return undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
class ZabbixQueryTemplateGroupPermissionsRequest extends ZabbixRequest<
|
|
{
|
|
groupid: string,
|
|
name: string
|
|
}[]> {
|
|
constructor(authToken?: string | null, cookie?: string | null) {
|
|
super("templategroup.get.permissions", authToken, cookie);
|
|
}
|
|
|
|
createZabbixParams(args?: ParsedArgs) {
|
|
return {
|
|
...super.createZabbixParams(args),
|
|
output: [
|
|
"groupid",
|
|
"name"
|
|
],
|
|
searchWildcardsEnabled: true,
|
|
search: {
|
|
name: [
|
|
ZabbixPermissionsHelper.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX + "/*"
|
|
]
|
|
}
|
|
};
|
|
}
|
|
}
|
|
interface ZabbixUserGroupResponse {
|
|
usrgrpid: string,
|
|
name: string,
|
|
gui_access: string,
|
|
users_status: string,
|
|
templategroup_rights:
|
|
{
|
|
id: string,
|
|
permission: Permission
|
|
}[]
|
|
}
|
|
class ZabbixQueryUserGroupPermissionsRequest extends ZabbixRequest<ZabbixUserGroupResponse[]> {
|
|
constructor(authToken?: string | null, cookie?: string | null) {
|
|
super("usergroup.get.permissions", authToken, cookie);
|
|
}
|
|
|
|
createZabbixParams(args?: ParsedArgs) {
|
|
return {
|
|
...super.createZabbixParams(args),
|
|
"output": [
|
|
"usrgrpid",
|
|
"name",
|
|
"gui_access",
|
|
"users_status"
|
|
], ...args?.zabbix_params,
|
|
"selectTemplateGroupRights": [
|
|
"id",
|
|
"permission"
|
|
]
|
|
};
|
|
}
|
|
}
|
|
|
|
export class ZabbixPermissionsHelper {
|
|
private static permissionObjectNameCache: Map<string, string | null> = new Map()
|
|
public static ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX = Config.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX
|
|
|
|
public static async getUserPermissions(zabbixAPI: ZabbixAPI, zabbixAuthToken?: string, cookie?: string,
|
|
objectNames?: InputMaybe<string[]> | undefined): Promise<UserPermission[]> {
|
|
return Array.from((await this.getUserPermissionNumbers(zabbixAPI, zabbixAuthToken, cookie, objectNames)).entries()).map(value => {
|
|
return {
|
|
objectName: value[0],
|
|
permission: this.mapPermissionToZabbixEnum(value[1])
|
|
}
|
|
});
|
|
}
|
|
|
|
public static async getUserPermissionNumbers(zabbixAPI: ZabbixAPI, zabbixAuthToken?: string | null, cookie?: string | null, objectNamesFilter?: InputMaybe<string[]> | undefined): Promise<Map<string, PermissionNumber>> {
|
|
const userGroupPermissions = await new ZabbixQueryUserGroupPermissionsRequest(zabbixAuthToken, cookie).executeRequestThrowError(zabbixAPI)
|
|
|
|
// Prepare the list of templateIds that are not loaded yet
|
|
const templateIdsToLoad = new Set(userGroupPermissions.flatMap(usergroup => usergroup.templategroup_rights.map(templateGroupRight => templateGroupRight.id)));
|
|
|
|
// Remove all templateIds that are already in the permissionObjectNameCache
|
|
templateIdsToLoad.forEach(id => {
|
|
if (this.permissionObjectNameCache.has(id)) {
|
|
templateIdsToLoad.delete(id);
|
|
}
|
|
})
|
|
|
|
if (templateIdsToLoad.size > 0) {
|
|
// Load all templateIds that are not in the permissionObjectNameCache
|
|
const missingPermissionGroupNames = await new ZabbixQueryTemplateGroupPermissionsRequest(zabbixAuthToken, cookie)
|
|
.executeRequestThrowError(zabbixAPI, new ParsedArgs({groupids: Array.from(templateIdsToLoad)}));
|
|
missingPermissionGroupNames.forEach(group => {
|
|
this.permissionObjectNameCache.set(group.groupid, group.name.replace(ZabbixPermissionsHelper.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX + "/", ""))
|
|
})
|
|
}
|
|
|
|
// Merge the permissions from the user groups. The merge function will first merge the permissions from the template groups
|
|
let permissions = new Map<string, PermissionNumber>();
|
|
userGroupPermissions.forEach(usergroup => {
|
|
permissions = this.mergeTemplateGroupPermissions(usergroup, permissions, objectNamesFilter);
|
|
})
|
|
return permissions;
|
|
}
|
|
|
|
|
|
private static mergeTemplateGroupPermissions(usergroup: ZabbixUserGroupResponse,
|
|
currentTemplateGroupPermissions: Map<string, PermissionNumber>,
|
|
objectNames: InputMaybe<string[]> | undefined): Map<string, PermissionNumber> {
|
|
// First, we have to find the minimum permission for each template group as this is always superseeding the higher permission if it is set within a user group
|
|
let minPermissionsInUserGroup: Map<string, PermissionNumber> = new Map();
|
|
|
|
let objectNamesFilter = this.createMatcherFromWildcardArray(objectNames);
|
|
|
|
usergroup.templategroup_rights.forEach(templateGroupPermission => {
|
|
const objectName = this.permissionObjectNameCache.get(templateGroupPermission.id);
|
|
if (objectName && (objectNamesFilter == undefined || objectNamesFilter.test(objectName))) {
|
|
const permissionValue = Number(templateGroupPermission.permission) as PermissionNumber;
|
|
const minPermissionWithinThisGroup = minPermissionsInUserGroup.get(objectName);
|
|
if (minPermissionWithinThisGroup == undefined || minPermissionWithinThisGroup > permissionValue) {
|
|
minPermissionsInUserGroup.set(objectName, permissionValue);
|
|
}
|
|
}
|
|
})
|
|
|
|
// Then we have to find the highest permission compared to the permissions resulting from other user groups, as on a
|
|
// user group level the higher permission is always superseeding the lower permission
|
|
minPermissionsInUserGroup.forEach((minPermissionInUserGroup, objectName) => {
|
|
const maxPermissionBetweenGroups = currentTemplateGroupPermissions.get(objectName);
|
|
if (maxPermissionBetweenGroups == undefined || maxPermissionBetweenGroups < minPermissionInUserGroup) {
|
|
currentTemplateGroupPermissions.set(objectName, minPermissionInUserGroup);
|
|
}
|
|
})
|
|
return currentTemplateGroupPermissions;
|
|
}
|
|
|
|
private static mapZabbixPermission(zabbixPermission: Permission): PermissionNumber {
|
|
switch (zabbixPermission) {
|
|
case Permission.Read:
|
|
return PermissionNumber.Read
|
|
case Permission.ReadWrite:
|
|
return PermissionNumber.ReadWrite
|
|
case Permission.Deny:
|
|
default:
|
|
return PermissionNumber.Deny
|
|
}
|
|
}
|
|
|
|
private static mapPermissionToZabbixEnum(permission: PermissionNumber): Permission {
|
|
switch (permission) {
|
|
case PermissionNumber.Read:
|
|
return Permission.Read
|
|
case PermissionNumber.ReadWrite:
|
|
return Permission.ReadWrite
|
|
case PermissionNumber.Deny:
|
|
default:
|
|
return Permission.Deny
|
|
}
|
|
}
|
|
|
|
private static createMatcherFromWildcardArray(array: InputMaybe<string[]> | undefined): RegExp | undefined {
|
|
if (!array) {
|
|
return undefined;
|
|
}
|
|
// Escape all values in the array and create regexp that allows the * wildcard which will be a .* in the regexp
|
|
return new RegExp(array.map(value => "^" + this.escapeRegExp(value).replace(/\\\*/gi, ".*") + "$").join("|"));
|
|
}
|
|
|
|
private static escapeRegExp(value: string) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
}
|
|
|
|
public static async hasUserPermissions(zabbixAPI: ZabbixAPI, args: QueryHasPermissionsArgs, zabbixAuthToken?: string | null, cookie?: string | null): Promise<boolean> {
|
|
let permissions = await this.getUserPermissionNumbers(zabbixAPI, zabbixAuthToken, cookie);
|
|
for (const permission of args.permissions) {
|
|
const existingPermission = permissions.get(permission.objectName);
|
|
if (permission.permission != Permission.Deny) {
|
|
if (existingPermission == undefined || existingPermission < this.mapZabbixPermission(permission.permission)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|