refactor!: Cleanup zabbix api access and remove unused classes
This commit is contained in:
parent
a89c3eeea7
commit
da86c726db
9 changed files with 246 additions and 624 deletions
|
|
@ -1,24 +1,26 @@
|
||||||
import {
|
import {
|
||||||
DeviceCommunicationType,
|
DeviceCommunicationType,
|
||||||
DeviceStatus,
|
DeviceStatus,
|
||||||
|
Host,
|
||||||
MutationCreateHostArgs,
|
MutationCreateHostArgs,
|
||||||
MutationImportHostsArgs,
|
|
||||||
MutationImportHostGroupsArgs,
|
MutationImportHostGroupsArgs,
|
||||||
|
MutationImportHostsArgs,
|
||||||
MutationImportUserRightsArgs,
|
MutationImportUserRightsArgs,
|
||||||
Permission,
|
Permission,
|
||||||
QueryAllHostsArgs,
|
|
||||||
QueryAllHostGroupsArgs,
|
QueryAllHostGroupsArgs,
|
||||||
|
QueryAllHostsArgs,
|
||||||
|
QueryExportHostValueHistoryArgs,
|
||||||
QueryExportUserRightsArgs,
|
QueryExportUserRightsArgs,
|
||||||
QueryHasPermissionsArgs,
|
QueryHasPermissionsArgs,
|
||||||
QueryUserPermissionsArgs,
|
QueryUserPermissionsArgs,
|
||||||
Resolvers,
|
Resolvers,
|
||||||
StorageItemType, Host, QueryExportHostValueHistoryArgs, Device,
|
StorageItemType,
|
||||||
} from "../schema/generated/graphql.js";
|
} from "../schema/generated/graphql.js";
|
||||||
|
|
||||||
import {HostImporter} from "../execution/host_importer";
|
import {HostImporter} from "../execution/host_importer";
|
||||||
import {HostValueExporter} from "../execution/host_exporter";
|
import {HostValueExporter} from "../execution/host_exporter";
|
||||||
import {logger} from "../logging/logger.js";
|
import {logger} from "../logging/logger.js";
|
||||||
import {ParsedArgs, ZabbixPermissionsHelper, ZabbixRequest} from "../datasources/zabbix-request.js";
|
import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js";
|
||||||
import {ZabbixCreateHostRequest, ZabbixQueryHostsRequestWithItemsAndInventory,} from "../datasources/zabbix-hosts.js";
|
import {ZabbixCreateHostRequest, ZabbixQueryHostsRequestWithItemsAndInventory,} from "../datasources/zabbix-hosts.js";
|
||||||
import {ZabbixQueryHostgroupsParams, ZabbixQueryHostgroupsRequest} from "../datasources/zabbix-hostgroups.js";
|
import {ZabbixQueryHostgroupsParams, ZabbixQueryHostgroupsRequest} from "../datasources/zabbix-hostgroups.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,6 +37,7 @@ import {
|
||||||
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api";
|
import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-api";
|
||||||
import {GraphQLInterfaceType, GraphQLList} from "graphql/type";
|
import {GraphQLInterfaceType, GraphQLList} from "graphql/type";
|
||||||
import {isDevice} from "./resolver_helpers";
|
import {isDevice} from "./resolver_helpers";
|
||||||
|
import {ZabbixPermissionsHelper} from "../datasources/zabbix-permissions";
|
||||||
|
|
||||||
|
|
||||||
export function createResolvers(): Resolvers {
|
export function createResolvers(): Resolvers {
|
||||||
|
|
@ -66,7 +69,7 @@ export function createResolvers(): Resolvers {
|
||||||
zabbixAPI, new ParsedArgs(args))
|
zabbixAPI, new ParsedArgs(args))
|
||||||
},
|
},
|
||||||
logout: async (_parent, _args, {zabbixAuthToken, cookie}: any) => {
|
logout: async (_parent, _args, {zabbixAuthToken, cookie}: any) => {
|
||||||
return await new ZabbixRequest<any>("user.logout", undefined, cookie).executeRequestThrowError(zabbixAPI);
|
return await new ZabbixRequest<any>("user.logout", zabbixAuthToken, cookie).executeRequestThrowError(zabbixAPI);
|
||||||
},
|
},
|
||||||
|
|
||||||
allHosts: async (_parent: any, args: QueryAllHostsArgs, {
|
allHosts: async (_parent: any, args: QueryAllHostsArgs, {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const createZabbixHierarchicalDeviceTagsResolver =
|
||||||
}
|
}
|
||||||
export async function schema_loader(): Promise<GraphQLSchema> {
|
export async function schema_loader(): Promise<GraphQLSchema> {
|
||||||
const resolvers = createResolvers();
|
const resolvers = createResolvers();
|
||||||
let typeDefs: string = readFileSync('./schema.graphql', {encoding: 'utf-8'});
|
let typeDefs: string = readFileSync('./src/schema/*.graphql', {encoding: 'utf-8'});
|
||||||
if (process.env.ADDITIONAL_SCHEMAS) {
|
if (process.env.ADDITIONAL_SCHEMAS) {
|
||||||
for (const schema of process.env.ADDITIONAL_SCHEMAS.split(",")){
|
for (const schema of process.env.ADDITIONAL_SCHEMAS.split(",")){
|
||||||
typeDefs += readFileSync(schema, {encoding: 'utf-8'});
|
typeDefs += readFileSync(schema, {encoding: 'utf-8'});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
CacheOptions,
|
|
||||||
DataSourceConfig,
|
DataSourceConfig,
|
||||||
DataSourceFetchResult,
|
DataSourceFetchResult,
|
||||||
DataSourceRequest,
|
DataSourceRequest,
|
||||||
|
|
@ -26,13 +25,11 @@ export class ZabbixAPI
|
||||||
override async fetch<Object>(path: string, incomingRequest: DataSourceRequest = {}): Promise<DataSourceFetchResult<Object>> {
|
override async fetch<Object>(path: string, incomingRequest: DataSourceRequest = {}): Promise<DataSourceFetchResult<Object>> {
|
||||||
logger.debug(`Zabbix request path=${path}, body=${JSON.stringify(incomingRequest.body).substring(0, ZabbixAPI.MAX_LOG_REQUEST_BODY_LIMIT_LENGTH)} (...)`)
|
logger.debug(`Zabbix request path=${path}, body=${JSON.stringify(incomingRequest.body).substring(0, ZabbixAPI.MAX_LOG_REQUEST_BODY_LIMIT_LENGTH)} (...)`)
|
||||||
|
|
||||||
let response_promise_original
|
let response_promise: Promise<DataSourceFetchResult<Object>> = super.fetch("api_jsonrpc.php", incomingRequest);
|
||||||
try {
|
|
||||||
const response_promise: Promise<DataSourceFetchResult<Object>> = super.fetch("api_jsonrpc.php", incomingRequest);
|
|
||||||
try {
|
try {
|
||||||
const response = await response_promise;
|
const response = await response_promise;
|
||||||
const body = response.parsedBody;
|
const body = response.parsedBody;
|
||||||
return await new Promise!<DataSourceFetchResult<Object>>((resolve, reject) => {
|
return await new Promise!<DataSourceFetchResult<Object>>((resolve) => {
|
||||||
if (body && body.hasOwnProperty("result")) {
|
if (body && body.hasOwnProperty("result")) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let result: any = body["result"];
|
let result: any = body["result"];
|
||||||
|
|
@ -74,17 +71,11 @@ export class ZabbixAPI
|
||||||
logger.error(msg);
|
logger.error(msg);
|
||||||
return response_promise
|
return response_promise
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
let msg = `Unable to retrieve response for request body=${JSON.stringify(incomingRequest.body)}: ${JSON.stringify(e)}`
|
|
||||||
logger.error(msg)
|
|
||||||
// @ts-ignore
|
|
||||||
return response_promise_original
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public post<TResult = any>(path: string, request?: PostRequest<CacheOptions>): Promise<TResult> {
|
public post<TResult = any>(path: string, request?: PostRequest): Promise<TResult> {
|
||||||
return super.post(path, request);
|
return super.post(path, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import {ZabbixAPI} from "./zabbix-api.js";
|
import {SortOrder, StorageItemType} from "../schema/generated/graphql.js";
|
||||||
import {ApiError, SortOrder, StorageItemType} from "../schema/generated/graphql.js";
|
|
||||||
import {ZabbixCreateOrUpdateStorageItemRequest} from "./zabbix-items.js";
|
|
||||||
import {ZabbixForceCacheReloadRequest} from "./zabbix-script.js";
|
|
||||||
import {logger} from "../logging/logger.js";
|
|
||||||
import {ApiErrorCode} from "../model/model_enum_values.js";
|
|
||||||
import {ParsedArgs, ZabbixParams, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
|
import {ParsedArgs, ZabbixParams, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
|
||||||
import {sleep} from "../common_utils";
|
|
||||||
|
|
||||||
export interface ZabbixValue {
|
export interface ZabbixValue {
|
||||||
key?: string,
|
key?: string,
|
||||||
|
|
@ -56,73 +50,3 @@ export class ZabbixQueryHistoryRequest extends ZabbixRequest<ZabbixExportValue[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ZabbixHistoryPushResult {
|
|
||||||
response: string,
|
|
||||||
data: { itemid: string, error?: string[] | ApiError }[],
|
|
||||||
error?: ApiError | string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixHistoryPushRequest extends ZabbixRequest<ZabbixHistoryPushResult> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("history.push", authToken, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixStoreObjectInItemHistoryRequest extends ZabbixRequest<ZabbixHistoryPushResult> {
|
|
||||||
// After creating an item or host zabbix needs some time before the created object can be referenced in other
|
|
||||||
// operations - the reason is the config-cache. In case of having ZBX_CACHEUPDATEFREQUENCY=1 (seconds) set within the
|
|
||||||
// Zabbix - config the delay of 1 second will be sufficient
|
|
||||||
private static readonly ZABBIX_DELAY_UNTIL_CONFIG_CHANGED: number = 0
|
|
||||||
public itemid: number | undefined
|
|
||||||
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("history.push.jsonobject", authToken, cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepare(zabbixAPI: ZabbixAPI, args?: ParsedArgs): Promise<any> {
|
|
||||||
// Create or update zabbix Item
|
|
||||||
let success = false;
|
|
||||||
this.itemid = Number(args?.getParam("itemid"))
|
|
||||||
let timeoutForValueUpdate = this.itemid ? 0 : ZabbixStoreObjectInItemHistoryRequest.ZABBIX_DELAY_UNTIL_CONFIG_CHANGED;
|
|
||||||
|
|
||||||
// Create or update controlprogram - item
|
|
||||||
let result: {
|
|
||||||
"itemids": string[]
|
|
||||||
} | undefined = await new ZabbixCreateOrUpdateStorageItemRequest(
|
|
||||||
this.itemid ? "item.update.storeiteminhistory" : "item.create.storeiteminhistory",
|
|
||||||
this.authToken, this.cookie).executeRequestThrowError(zabbixAPI, args)
|
|
||||||
|
|
||||||
// logger.debug(`Create/update item itemid=${this.itemid}, hostid=${this.zabbixHostId} lead to result=`, JSON.stringify(result));
|
|
||||||
|
|
||||||
if (result && result.hasOwnProperty("itemids") && result.itemids.length > 0) {
|
|
||||||
this.itemid = Number(result.itemids[0]);
|
|
||||||
let scriptExecResult =
|
|
||||||
await new ZabbixForceCacheReloadRequest(this.authToken, this.cookie).executeRequestThrowError(zabbixAPI)
|
|
||||||
if (scriptExecResult.response != "success") {
|
|
||||||
logger.error(`cache reload not successful: ${scriptExecResult.value}`)
|
|
||||||
}
|
|
||||||
await sleep(timeoutForValueUpdate).promise
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.itemid) {
|
|
||||||
this.prepResult = {
|
|
||||||
error: {
|
|
||||||
message: "Unable to create/update item",
|
|
||||||
code: ApiErrorCode.ZABBIX_NO_ITEM_PUSH_ITEM,
|
|
||||||
path: this.path,
|
|
||||||
args: args,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
return {
|
|
||||||
itemid: this.itemid,
|
|
||||||
value: JSON.stringify(args?.getParam("value"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
zabbixSuperAuthToken
|
zabbixSuperAuthToken
|
||||||
} from "./zabbix-api";
|
} from "./zabbix-api";
|
||||||
import {logger} from "../logging/logger";
|
import {logger} from "../logging/logger";
|
||||||
|
import {ZabbixRequestWithPermissions} from "./zabbix-permissions";
|
||||||
|
|
||||||
export interface CreateHostGroupResult {
|
export interface CreateHostGroupResult {
|
||||||
groupids: string[]
|
groupids: string[]
|
||||||
|
|
@ -20,34 +21,12 @@ const hostGroupReadWritePermissions = {
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostGroupReadPermissions = {
|
export class ZabbixCreateHostGroupRequest extends ZabbixRequestWithPermissions<CreateHostGroupResult> {
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
objectName: "Hostgroup/ConstructionSite",
|
|
||||||
permission: Permission.Read
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixCreateHostGroupRequest extends ZabbixRequest<CreateHostGroupResult> {
|
|
||||||
constructor(_authToken?: string | null, cookie?: string) {
|
constructor(_authToken?: string | null, cookie?: string) {
|
||||||
super("hostgroup.create", zabbixSuperAuthToken, cookie, hostGroupReadWritePermissions);
|
super("hostgroup.create", zabbixSuperAuthToken, cookie, hostGroupReadWritePermissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZabbixDeleteHostGroupRequest extends ZabbixRequest<{
|
|
||||||
"groupids": string []
|
|
||||||
}> {
|
|
||||||
constructor(_authToken?: string | null, cookie?: string) {
|
|
||||||
super("hostgroup.delete", zabbixSuperAuthToken, cookie, {
|
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
objectName: "Hostgroup/ConstructionSite",
|
|
||||||
permission: Permission.ReadWrite
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixQueryHostgroupsParams extends ParsedArgs {
|
export class ZabbixQueryHostgroupsParams extends ParsedArgs {
|
||||||
search_name: string | undefined
|
search_name: string | undefined
|
||||||
|
|
||||||
|
|
@ -66,7 +45,7 @@ export type ZabbixQueryHostgroupsResult = {
|
||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZabbixQueryHostgroupsRequest extends ZabbixRequest<ZabbixQueryHostgroupsResult[],
|
export class ZabbixQueryHostgroupsRequest extends ZabbixRequestWithPermissions<ZabbixQueryHostgroupsResult[],
|
||||||
ZabbixQueryHostgroupsParams> {
|
ZabbixQueryHostgroupsParams> {
|
||||||
constructor(authToken?: string | null, cookie?: string | null, hostGroupReadPermissions?: any) {
|
constructor(authToken?: string | null, cookie?: string | null, hostGroupReadPermissions?: any) {
|
||||||
super("hostgroup.get", authToken, cookie, hostGroupReadPermissions,);
|
super("hostgroup.get", authToken, cookie, hostGroupReadPermissions,);
|
||||||
|
|
|
||||||
|
|
@ -62,26 +62,6 @@ export class ZabbixQueryHostsMetaRequest extends ZabbixQueryHostsGenericRequest<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZabbixQueryHostsWithDeviceTypeMetaRequest extends ZabbixQueryHostsGenericRequest<Host[]> {
|
|
||||||
public static PATH = "host.get.meta_with_device_type"
|
|
||||||
|
|
||||||
constructor(authToken?: string | null, cookie?: string | null) {
|
|
||||||
super(ZabbixQueryHostsWithDeviceTypeMetaRequest.PATH, authToken, cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
return {
|
|
||||||
...super.createZabbixParams(args),
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
"tag": "deviceType",
|
|
||||||
"operator": 4
|
|
||||||
}
|
|
||||||
],
|
|
||||||
inheritedTags: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixQueryHostsGenericRequestWithItems<T extends ZabbixResult> extends ZabbixQueryHostsGenericRequest<T> {
|
export class ZabbixQueryHostsGenericRequestWithItems<T extends ZabbixResult> extends ZabbixQueryHostsGenericRequest<T> {
|
||||||
constructor(path: string, authToken?: string | null, cookie?: string) {
|
constructor(path: string, authToken?: string | null, cookie?: string) {
|
||||||
|
|
@ -164,30 +144,6 @@ export class ZabbixQueryHostsRequestWithItemsAndInventory extends ZabbixQueryHos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZabbixQueryHostWithInventoryRequest extends ZabbixRequest<any> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("host.get.with_inventory", authToken, cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
return {
|
|
||||||
...super.createZabbixParams(args),
|
|
||||||
selectInventory: [
|
|
||||||
"location", "location_lat", "location_lon"
|
|
||||||
],
|
|
||||||
output: [
|
|
||||||
"hostid",
|
|
||||||
"host",
|
|
||||||
"name",
|
|
||||||
"hostgroup",
|
|
||||||
"description",
|
|
||||||
"parentTemplates"
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const isZabbixCreateHostInputParams = (value: ZabbixParams): value is ZabbixCreateHostInputParams => "host" in value && !!value.host;
|
const isZabbixCreateHostInputParams = (value: ZabbixParams): value is ZabbixCreateHostInputParams => "host" in value && !!value.host;
|
||||||
|
|
||||||
export interface ZabbixCreateHostInputParams extends ZabbixParams {
|
export interface ZabbixCreateHostInputParams extends ZabbixParams {
|
||||||
|
|
@ -256,45 +212,3 @@ export class ZabbixCreateHostRequest extends ZabbixRequest<CreateHostResponse> {
|
||||||
return args?.zabbix_params || {};
|
return args?.zabbix_params || {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ZabbixQueryHostRequest extends ZabbixQueryHostsGenericRequest<any> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string | null) {
|
|
||||||
super("host.get", authToken, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class ZabbixCreateOrFindHostRequest extends ZabbixCreateHostRequest {
|
|
||||||
|
|
||||||
constructor(protected groupid: number, protected templateid: number, authToken?: string | null, cookie?: string,) {
|
|
||||||
super(authToken, cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
return super.createZabbixParams(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepare(zabbixAPI: ZabbixAPI, args?: ParsedArgs) {
|
|
||||||
// Lookup host of appropriate type (by template) and groupName
|
|
||||||
// or create one if not found. If multiple hosts are found the first
|
|
||||||
// will be taken
|
|
||||||
|
|
||||||
let queryHostArgs = new ParsedArgs({
|
|
||||||
groupids: this.groupid,
|
|
||||||
templateids: this.templateid,
|
|
||||||
});
|
|
||||||
let hosts: {
|
|
||||||
hostid: number
|
|
||||||
}[] = await new ZabbixQueryHostRequest(this.authToken, this.cookie)
|
|
||||||
.executeRequestThrowError(zabbixAPI, queryHostArgs)
|
|
||||||
// logger.debug("Query hosts args=", JSON.stringify(queryHostArgs), "lead to result=", JSON.stringify(hosts));
|
|
||||||
if (hosts && hosts.length > 0) {
|
|
||||||
// If we found a host and return it as prep result the execution of the create host request will be skipped
|
|
||||||
this.prepResult = {
|
|
||||||
hostids: [hosts[0].hostid]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.prepResult;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,6 @@
|
||||||
import {ParsedArgs, ZabbixParams, ZabbixRequest, ZabbixResult, ZabbixValueType} from "./zabbix-request.js";
|
import {ParsedArgs, ZabbixRequest} from "./zabbix-request.js";
|
||||||
import {ZabbixItem} from "../schema/generated/graphql";
|
import {ZabbixItem} from "../schema/generated/graphql";
|
||||||
|
|
||||||
export class ZabbixQueryItemsMetaRequest extends ZabbixRequest<any> {
|
|
||||||
createZabbixParams(args?: ParsedArgs) {
|
|
||||||
return {
|
|
||||||
"templated": false,
|
|
||||||
output: [
|
|
||||||
"itemid",
|
|
||||||
"key_",
|
|
||||||
"hostid"
|
|
||||||
], ...args?.zabbix_params
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class ZabbixQueryItemsRequest extends ZabbixRequest<ZabbixItem[]> {
|
export class ZabbixQueryItemsRequest extends ZabbixRequest<ZabbixItem[]> {
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
constructor(authToken?: string | null, cookie?: string) {
|
||||||
|
|
@ -47,128 +34,3 @@ export class ZabbixQueryItemsRequest extends ZabbixRequest<ZabbixItem[]> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class ZabbixQueryItemsByIdRequest extends ZabbixRequest<ZabbixItem[]> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("item.get.itembyid", authToken, cookie);
|
|
||||||
}
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
let filter: { key_: string | null } | null = null
|
|
||||||
if (args?.zabbix_params?.hasOwnProperty("id")) {
|
|
||||||
// @ts-ignore
|
|
||||||
args.zabbix_params["filter"] = {
|
|
||||||
// @ts-ignore
|
|
||||||
...args?.zabbix_params.filter, "key_": args?.zabbix_params.id
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
delete args.zabbix_params["id"]
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
filter: filter,
|
|
||||||
"selectTags": ["tag", "value"],
|
|
||||||
"inheritedTags": true,
|
|
||||||
"output": [
|
|
||||||
"lastvalue",
|
|
||||||
"lastclock",
|
|
||||||
"value_type",
|
|
||||||
"hostid",
|
|
||||||
"itemid",
|
|
||||||
"name",
|
|
||||||
"status",
|
|
||||||
"key_"
|
|
||||||
], ...args?.zabbix_params,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface ZabbixStoreValueInItemParams extends ZabbixParams {
|
|
||||||
hostid?: number
|
|
||||||
itemid?: number
|
|
||||||
key: string
|
|
||||||
name: string
|
|
||||||
tags: {
|
|
||||||
tag: string,
|
|
||||||
value?: string
|
|
||||||
}[]
|
|
||||||
value: Object
|
|
||||||
}
|
|
||||||
|
|
||||||
const isStoreValueInItem = (value: ZabbixParams): value is ZabbixStoreValueInItemParams =>
|
|
||||||
"hostid" in value && !!value.hostid && "name" in value && "key" in value && "value" in value;
|
|
||||||
|
|
||||||
const isUpdateValueInItemParams = (value: ZabbixParams): value is ZabbixUpdateValueInItemParams =>
|
|
||||||
"itemid" in value && !!value.itemid && isStoreValueInItem(value);
|
|
||||||
|
|
||||||
export interface ZabbixUpdateValueInItemParams extends ZabbixStoreValueInItemParams {
|
|
||||||
itemid: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ZabbixItemType {
|
|
||||||
ZABBIX_TRAPPER = 2,
|
|
||||||
ZABBIX_SCRIPT = 21
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixCreateOrUpdateStorageItemRequest extends ZabbixRequest<any> {
|
|
||||||
static MAX_ZABBIX_ITEM_STORAGE_PERIOD = "9125d"; // Maximum possible value is 25 years, which corresponds to 9125 days
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs): ZabbixParams {
|
|
||||||
if (args && isStoreValueInItem(args?.zabbix_params)) {
|
|
||||||
// Attention!! Zabbix status
|
|
||||||
// can not be used as expected:
|
|
||||||
// 1. Status 0 means enabled, all other values mean disabled
|
|
||||||
// 2. If the status of the item is disabled the value will not be
|
|
||||||
// evaluated - this means we can't use the item status to reflect
|
|
||||||
// the activation status of the controlProgram, as we also want
|
|
||||||
// to read the values of disabled controlPrograms..
|
|
||||||
let createOrUpdateItemParams = {
|
|
||||||
key_: args.zabbix_params.key,
|
|
||||||
name: args.zabbix_params.name,
|
|
||||||
tags: args.zabbix_params.tags,
|
|
||||||
"type": ZabbixItemType.ZABBIX_TRAPPER.valueOf(),
|
|
||||||
"history": ZabbixCreateOrUpdateStorageItemRequest.MAX_ZABBIX_ITEM_STORAGE_PERIOD,
|
|
||||||
"value_type": ZabbixValueType.TEXT.valueOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUpdateValueInItemParams(args.zabbix_params)) {
|
|
||||||
return {
|
|
||||||
itemid: args.zabbix_params.itemid,
|
|
||||||
...createOrUpdateItemParams
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
hostid: args.zabbix_params.hostid,
|
|
||||||
...createOrUpdateItemParams
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return args?.zabbix_params || {};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ZabbixDeleteItemResponse extends ZabbixResult {
|
|
||||||
itemids: {
|
|
||||||
itemid: string | string[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixDeleteItemRequest extends ZabbixRequest<ZabbixDeleteItemResponse> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("item.delete", authToken, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ZabbixCreateOrUpdateItemResponse extends ZabbixResult {
|
|
||||||
"itemids": string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ZabbixCreateOrUpdateItemRequest extends ZabbixRequest<ZabbixCreateOrUpdateItemResponse> {
|
|
||||||
constructor(path: string, authToken?: string | null, cookie?: string) {
|
|
||||||
super(path, authToken, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,82 @@
|
||||||
import {ParsedArgs, ZabbixRequest} from "./zabbix-request.js";
|
import {ParsedArgs, ZabbixErrorResult, ZabbixRequest, ZabbixResult} from "./zabbix-request.js";
|
||||||
|
import {ZabbixAPI} from "./zabbix-api";
|
||||||
|
import {InputMaybe, Permission, QueryHasPermissionsArgs, UserPermission} from "../schema/generated/graphql";
|
||||||
|
import {ApiErrorCode, PermissionNumber} from "../model/model_enum_values";
|
||||||
|
|
||||||
|
|
||||||
|
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<
|
class ZabbixQueryTemplateGroupPermissionsRequest extends ZabbixRequest<
|
||||||
{
|
{
|
||||||
groupid: string,
|
groupid: string,
|
||||||
name: string
|
name: string
|
||||||
}[]> {
|
}[]> {
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
constructor(authToken?: string | null, cookie?: string | null) {
|
||||||
super("templategroup.get.permissions", authToken, cookie);
|
super("templategroup.get.permissions", authToken, cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs) {
|
createZabbixParams(args?: ParsedArgs) {
|
||||||
return {
|
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": [
|
"output": [
|
||||||
"usrgrpid",
|
"usrgrpid",
|
||||||
"name",
|
"name",
|
||||||
|
|
@ -26,41 +91,127 @@ class ZabbixQueryTemplateGroupPermissionsRequest extends ZabbixRequest<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ZabbixPermissionsHelper {
|
||||||
|
private static permissionObjectNameCache: Map<string, string | null> = new Map()
|
||||||
|
public static ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX = process.env.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX || "Permissions"
|
||||||
|
|
||||||
interface ZabbixUserGroupResponse {
|
public static async getUserPermissions(zabbixAPI: ZabbixAPI, zabbixAuthToken?: string, cookie?: string,
|
||||||
usrgrpid: string,
|
objectNames?: InputMaybe<string[]> | undefined): Promise<UserPermission[]> {
|
||||||
name: string,
|
return Array.from((await this.getUserPermissionNumbers(zabbixAPI, zabbixAuthToken, cookie, objectNames)).entries()).map(value => {
|
||||||
gui_access: string,
|
|
||||||
users_status: string,
|
|
||||||
templategroup_rights:
|
|
||||||
{
|
|
||||||
id: string,
|
|
||||||
permission: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
class ZabbixQueryUserGroupPermissionsRequest extends ZabbixRequest<ZabbixUserGroupResponse[]> {
|
|
||||||
constructor(authToken?: string | null, cookie?: string) {
|
|
||||||
super("usergroup.get.permissions", authToken, cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
createZabbixParams(args?: ParsedArgs) {
|
|
||||||
return {
|
return {
|
||||||
...super.createZabbixParams(args),
|
objectName: value[0],
|
||||||
"params": {
|
permission: this.mapPermissionToZabbixEnum(value[1])
|
||||||
"output": [
|
|
||||||
"groupid",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"searchWildcardsEnabled": true,
|
|
||||||
"search": {
|
|
||||||
"name": [
|
|
||||||
"Permissions/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
"id": 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {ApiError, InputMaybe, QueryHasPermissionsArgs, UserPermission} from "../schema/generated/graphql.js";
|
import {ApiError} from "../schema/generated/graphql.js";
|
||||||
import {ZabbixAPI} from "./zabbix-api.js";
|
import {ZabbixAPI} from "./zabbix-api.js";
|
||||||
import {ApiErrorCode, Permission, PermissionNumber} from "../model/model_enum_values.js";
|
import {ApiErrorCode} from "../model/model_enum_values.js";
|
||||||
import {logger} from "../logging/logger.js";
|
import {logger} from "../logging/logger.js";
|
||||||
import {GraphQLError} from "graphql";
|
import {GraphQLError} from "graphql";
|
||||||
|
|
||||||
|
|
@ -31,10 +31,6 @@ export interface ZabbixWithTagsParams extends ZabbixParams {
|
||||||
tags?: { tag: string; operator: number; value: any; }[]
|
tags?: { tag: string; operator: number; value: any; }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ZabbixValueType {
|
|
||||||
TEXT = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ParsedArgs {
|
export class ParsedArgs {
|
||||||
public name_pattern?: string
|
public name_pattern?: string
|
||||||
public distinct_by_name?: boolean;
|
public distinct_by_name?: boolean;
|
||||||
|
|
@ -142,8 +138,7 @@ export class ZabbixRequest<T extends ZabbixResult, A extends ParsedArgs = Parsed
|
||||||
protected method: string
|
protected method: string
|
||||||
protected prepResult: T | ZabbixErrorResult | undefined = undefined
|
protected prepResult: T | ZabbixErrorResult | undefined = undefined
|
||||||
|
|
||||||
constructor(public path: string, public authToken?: string | null, public cookie?: string | null,
|
constructor(public path: string, public authToken?: string | null, public cookie?: string | null) {
|
||||||
protected permissionsNeeded?: QueryHasPermissionsArgs) {
|
|
||||||
this.method = path.split(".", 2).join(".");
|
this.method = path.split(".", 2).join(".");
|
||||||
this.requestBodyTemplate = new ZabbixRequestBody(this.method);
|
this.requestBodyTemplate = new ZabbixRequestBody(this.method);
|
||||||
}
|
}
|
||||||
|
|
@ -188,26 +183,11 @@ export class ZabbixRequest<T extends ZabbixResult, A extends ParsedArgs = Parsed
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepare(zabbixAPI: ZabbixAPI, args?: A): Promise<T | ZabbixErrorResult | undefined> {
|
|
||||||
// If prepare returns something else than undefined the execution will be skipped and the
|
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
|
// result returned
|
||||||
this.prepResult = await this.assureUserPermissions(zabbixAPI);
|
|
||||||
return this.prepResult;
|
return this.prepResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,189 +322,7 @@ export class ZabbixCreateOrUpdateRequest<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = process.env.ZABBIX_PERMISSION_TEMPLATE_GROUP_NAME_PREFIX || "Permissions"
|
|
||||||
|
|
||||||
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) {
|
|
||||||
let result = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue