From 75bbca59c0fcc62f2426636cf09d7cfd8b89e6dc Mon Sep 17 00:00:00 2001 From: madhavilosetty-intel Date: Tue, 7 Oct 2025 09:02:02 -0700 Subject: [PATCH] feat: enable http proxy for cira --- package-lock.json | 16 +- package.json | 4 +- .../tables/profileProxyConfigs.test.ts | 4 +- .../postgres/tables/profileProxyConfigs.ts | 4 +- src/data/postgres/tables/profiles.test.ts | 4 +- src/data/postgres/tables/profiles.ts | 4 +- src/data/postgres/tables/proxyConfigs.test.ts | 26 +- src/data/postgres/tables/proxyConfigs.ts | 49 ++- src/models/RCS.Config.ts | 8 +- .../admin/profiles/amtProfileValidator.ts | 4 +- src/routes/admin/profiles/edit.test.ts | 11 +- src/routes/admin/profiles/edit.ts | 21 +- src/routes/admin/proxy/create.test.ts | 25 +- src/routes/admin/proxy/create.ts | 11 +- src/routes/admin/proxy/delete.test.ts | 2 +- src/routes/admin/proxy/delete.ts | 12 +- src/routes/admin/proxy/edit.test.ts | 3 +- src/routes/admin/proxy/edit.ts | 17 +- src/routes/admin/proxy/get.test.ts | 4 +- src/routes/admin/proxy/get.ts | 8 +- src/routes/admin/proxy/index.ts | 4 +- src/routes/admin/proxy/proxyValidator.ts | 108 +++-- src/stateMachines/networkConfiguration.ts | 38 ++ src/stateMachines/proxyConfiguration.test.ts | 273 +++++++++++++ src/stateMachines/proxyConfiguration.ts | 247 ++++++++++++ src/stateMachines/unconfiguration.test.ts | 232 ++++++++++- src/stateMachines/unconfiguration.ts | 156 +++++++- .../collections/rps.postman_collection.json | 376 ++++++++++++++++-- 28 files changed, 1484 insertions(+), 187 deletions(-) create mode 100644 src/stateMachines/proxyConfiguration.test.ts create mode 100644 src/stateMachines/proxyConfiguration.ts diff --git a/package-lock.json b/package-lock.json index a26d2a4cc..3298e2131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.27.1", "license": "Apache-2.0", "dependencies": { - "@device-management-toolkit/wsman-messages": "^5.12.0", + "@device-management-toolkit/wsman-messages": "^5.13.0", "body-parser": "^2.2.0", "consul": "^2.0.1", "cors": "^2.8.5", @@ -56,7 +56,7 @@ "prettier-plugin-multiline-arrays": "^4.0.3", "sinon": "^21.0.0", "ts-jest": "^29.4.1", - "ts-jest-mock-import-meta": "^1.3.1", + "ts-jest-mock-import-meta": "^1.3.0", "ts-node": "^10.9.2", "typescript": "^5.8.3", "typescript-eslint": "8.45.0" @@ -774,9 +774,9 @@ } }, "node_modules/@device-management-toolkit/wsman-messages": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@device-management-toolkit/wsman-messages/-/wsman-messages-5.12.0.tgz", - "integrity": "sha512-agMI7Ed9MxyM8xU7Kp/hpgj/D4l09iNzd52ai4XxvQ9PIij7FDQ/7q2nwiD88yzRHmyMJYydfUf1CRArKA8aNw==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@device-management-toolkit/wsman-messages/-/wsman-messages-5.13.0.tgz", + "integrity": "sha512-+/WjBUupUn9QJcTzTmY7gKKhzCuiifpXSQCliyVgToMhOETuqNtzqysILS97/vhSrfnbvkZw1FBJuvDFBEk0yg==", "license": "Apache-2.0" }, "node_modules/@eslint-community/eslint-utils": { @@ -8440,9 +8440,9 @@ } }, "node_modules/ts-jest-mock-import-meta": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ts-jest-mock-import-meta/-/ts-jest-mock-import-meta-1.3.1.tgz", - "integrity": "sha512-KGrp9Nh/SdyrQs5hZvtkp0CFPOgAh3DL57NZgFRbtlvMyEo7XuXLbeyylmxFZGGu30pL338h9KxwSxrNDndygw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-jest-mock-import-meta/-/ts-jest-mock-import-meta-1.3.0.tgz", + "integrity": "sha512-xiuhc4DXRp35Lwy3bPB9X7utwyBJzp0tkse2qt3V3wxaCNSEOq54HyMxz75mvdG6LcOF4hawgb6vxEIpWnUe5A==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/package.json b/package.json index 0a0a9940d..e3d73637a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "ci-prettify": "npx prettier --check '**/*.{ts,js,yaml,css,scss,json}'" }, "dependencies": { - "@device-management-toolkit/wsman-messages": "^5.12.0", + "@device-management-toolkit/wsman-messages": "^5.13.0", "body-parser": "^2.2.0", "consul": "^2.0.1", "cors": "^2.8.5", @@ -93,7 +93,7 @@ "prettier-plugin-multiline-arrays": "^4.0.3", "sinon": "^21.0.0", "ts-jest": "^29.4.1", - "ts-jest-mock-import-meta": "^1.3.1", + "ts-jest-mock-import-meta": "^1.3.0", "ts-node": "^10.9.2", "typescript": "^5.8.3", "typescript-eslint": "8.45.0" diff --git a/src/data/postgres/tables/profileProxyConfigs.test.ts b/src/data/postgres/tables/profileProxyConfigs.test.ts index bdd6fbb89..4f2aec464 100644 --- a/src/data/postgres/tables/profileProxyConfigs.test.ts +++ b/src/data/postgres/tables/profileProxyConfigs.test.ts @@ -15,7 +15,7 @@ describe('profileproxyconfig tests', () => { let profilesProxyConfigsTable: ProfileProxyConfigsTable let querySpy: SpyInstance const proxyConfigs: ProfileProxyConfigs[] = [ - { profileName: 'proxyConfig', priority: 1 } as any + { name: 'proxyConfig', priority: 1 } as any ] const profileName = 'profileName' const tenantId = 'tenantId' @@ -38,7 +38,7 @@ describe('profileproxyconfig tests', () => { ` SELECT priority as "priority", - proxy_config_name as "profileName" + proxy_config_name as "name" FROM profiles_proxyconfigs WHERE profile_name = $1 and tenant_id = $2 ORDER BY priority`, diff --git a/src/data/postgres/tables/profileProxyConfigs.ts b/src/data/postgres/tables/profileProxyConfigs.ts index d7f9abfd9..f98e3fa3a 100644 --- a/src/data/postgres/tables/profileProxyConfigs.ts +++ b/src/data/postgres/tables/profileProxyConfigs.ts @@ -27,7 +27,7 @@ export class ProfileProxyConfigsTable implements IProfileProxyConfigsTable { ` SELECT priority as "priority", - proxy_config_name as "profileName" + proxy_config_name as "name" FROM profiles_proxyconfigs WHERE profile_name = $1 and tenant_id = $2 ORDER BY priority`, @@ -53,7 +53,7 @@ export class ProfileProxyConfigsTable implements IProfileProxyConfigsTable { } // Preparing data for inserting multiple rows const configs = proxyConfigs.map((config) => [ - config.profileName, + config.name, profileName, config.priority, tenantId diff --git a/src/data/postgres/tables/profiles.test.ts b/src/data/postgres/tables/profiles.test.ts index 29b4a35e5..763a8b0f9 100644 --- a/src/data/postgres/tables/profiles.test.ts +++ b/src/data/postgres/tables/profiles.test.ts @@ -131,7 +131,7 @@ describe('profiles tests', () => { COALESCE(json_agg(json_build_object('profileName',wc.wireless_profile_name, 'priority', wc.priority)) FILTER (WHERE wc.wireless_profile_name IS NOT NULL), '[]') AS "wifiConfigs", ip_sync_enabled as "ipSyncEnabled", local_wifi_sync_enabled as "localWifiSyncEnabled", - COALESCE(json_agg(json_build_object('profileName',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" + COALESCE(json_agg(json_build_object('name',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" FROM profiles p LEFT JOIN profiles_wirelessconfigs wc ON wc.profile_name = p.profile_name AND wc.tenant_id = p.tenant_id LEFT JOIN profiles_proxyconfigs pc ON pc.profile_name = p.profile_name AND pc.tenant_id = p.tenant_id @@ -191,7 +191,7 @@ describe('profiles tests', () => { COALESCE(json_agg(json_build_object('profileName',wc.wireless_profile_name, 'priority', wc.priority)) FILTER (WHERE wc.wireless_profile_name IS NOT NULL), '[]') AS "wifiConfigs", ip_sync_enabled as "ipSyncEnabled", local_wifi_sync_enabled as "localWifiSyncEnabled", - COALESCE(json_agg(json_build_object('profileName',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" + COALESCE(json_agg(json_build_object('name',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" FROM profiles p LEFT JOIN profiles_wirelessconfigs wc ON wc.profile_name = p.profile_name AND wc.tenant_id = p.tenant_id LEFT JOIN profiles_proxyconfigs pc ON pc.profile_name = p.profile_name AND pc.tenant_id = p.tenant_id diff --git a/src/data/postgres/tables/profiles.ts b/src/data/postgres/tables/profiles.ts index ca964d43e..822ca9661 100644 --- a/src/data/postgres/tables/profiles.ts +++ b/src/data/postgres/tables/profiles.ts @@ -79,7 +79,7 @@ export class ProfilesTable implements IProfilesTable { COALESCE(json_agg(json_build_object('profileName',wc.wireless_profile_name, 'priority', wc.priority)) FILTER (WHERE wc.wireless_profile_name IS NOT NULL), '[]') AS "wifiConfigs", ip_sync_enabled as "ipSyncEnabled", local_wifi_sync_enabled as "localWifiSyncEnabled", - COALESCE(json_agg(json_build_object('profileName',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" + COALESCE(json_agg(json_build_object('name',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" FROM profiles p LEFT JOIN profiles_wirelessconfigs wc ON wc.profile_name = p.profile_name AND wc.tenant_id = p.tenant_id LEFT JOIN profiles_proxyconfigs pc ON pc.profile_name = p.profile_name AND pc.tenant_id = p.tenant_id @@ -143,7 +143,7 @@ export class ProfilesTable implements IProfilesTable { COALESCE(json_agg(json_build_object('profileName',wc.wireless_profile_name, 'priority', wc.priority)) FILTER (WHERE wc.wireless_profile_name IS NOT NULL), '[]') AS "wifiConfigs", ip_sync_enabled as "ipSyncEnabled", local_wifi_sync_enabled as "localWifiSyncEnabled", - COALESCE(json_agg(json_build_object('profileName',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" + COALESCE(json_agg(json_build_object('name',pc.proxy_config_name, 'priority', pc.priority)) FILTER (WHERE pc.proxy_config_name IS NOT NULL), '[]') AS "proxyConfigs" FROM profiles p LEFT JOIN profiles_wirelessconfigs wc ON wc.profile_name = p.profile_name AND wc.tenant_id = p.tenant_id LEFT JOIN profiles_proxyconfigs pc ON pc.profile_name = p.profile_name AND pc.tenant_id = p.tenant_id diff --git a/src/data/postgres/tables/proxyConfigs.test.ts b/src/data/postgres/tables/proxyConfigs.test.ts index a53925bfa..efcecb906 100644 --- a/src/data/postgres/tables/proxyConfigs.test.ts +++ b/src/data/postgres/tables/proxyConfigs.test.ts @@ -24,10 +24,10 @@ describe('proxy configs tests', () => { let proxyConfigsTable: ProxyConfigsTable let querySpy: SpyInstance let proxyConfig: ProxyConfig - const profileName = 'proxyName' + const profileName = 'name' beforeEach(() => { proxyConfig = { - proxyName: 'proxy profile', + name: 'proxy profile', address: 'proxy.com', infoFormat: 201, // FQDN (201) port: 1000, @@ -97,7 +97,7 @@ describe('proxy configs tests', () => { expect(querySpy).toBeCalledWith( ` SELECT - proxy_config_name as "proxyName", + proxy_config_name as "name", address as "address", info_format as "infoFormat", port as "port", @@ -123,7 +123,7 @@ describe('proxy configs tests', () => { expect(querySpy).toBeCalledWith( ` SELECT - proxy_config_name as "proxyName", + proxy_config_name as "name", address as "address", info_format as "infoFormat", port as "port", @@ -226,7 +226,7 @@ describe('proxy configs tests', () => { const result = await proxyConfigsTable.insert(proxyConfig) expect(result).toBe(proxyConfig) - expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.proxyName, proxyConfig.tenantId) + expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.name, proxyConfig.tenantId) expect(querySpy).toBeCalledTimes(1) expect(querySpy).toBeCalledWith( ` @@ -234,7 +234,7 @@ describe('proxy configs tests', () => { (proxy_config_name, address, info_format, port, network_dns_suffix, creation_date, tenant_id) values($1, $2, $3, $4, $5, $6, $7)`, [ - proxyConfig.proxyName, + proxyConfig.name, proxyConfig.address, proxyConfig.infoFormat, proxyConfig.port, @@ -256,13 +256,13 @@ describe('proxy configs tests', () => { test('should NOT insert when duplicate name', async () => { querySpy.mockRejectedValueOnce({ code: '23505' }) await expect(proxyConfigsTable.insert(proxyConfig)).rejects.toThrow( - NETWORK_CONFIG_INSERTION_FAILED_DUPLICATE('Proxy', proxyConfig.proxyName) + NETWORK_CONFIG_INSERTION_FAILED_DUPLICATE('Proxy', proxyConfig.name) ) }) test('should NOT insert when unexpected exception', async () => { querySpy.mockRejectedValueOnce(new Error('unknown')) await expect(proxyConfigsTable.insert(proxyConfig)).rejects.toThrow( - NETWORK_CONFIG_ERROR('Proxy', proxyConfig.proxyName) + NETWORK_CONFIG_ERROR('Proxy', proxyConfig.name) ) }) }) @@ -273,7 +273,7 @@ describe('proxy configs tests', () => { getByNameSpy.mockResolvedValue(proxyConfig) const result = await proxyConfigsTable.update(proxyConfig) expect(result).toBe(proxyConfig) - expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.proxyName, proxyConfig.tenantId) + expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.name, proxyConfig.tenantId) expect(querySpy).toBeCalledTimes(1) expect(querySpy).toBeCalledWith( ` @@ -281,7 +281,7 @@ describe('proxy configs tests', () => { SET address=$2, info_format=$3, port=$4, network_dns_suffix=$5 WHERE proxy_config_name=$1 and tenant_id = $6`, [ - proxyConfig.proxyName, + proxyConfig.name, proxyConfig.address, proxyConfig.infoFormat, proxyConfig.port, @@ -307,7 +307,7 @@ describe('proxy configs tests', () => { const getByNameSpy = spyOn(proxyConfigsTable, 'getByName') getByNameSpy.mockResolvedValue(proxyConfig) await expect(proxyConfigsTable.update(proxyConfig)).rejects.toThrow( - NETWORK_CONFIG_ERROR('Proxy', proxyConfig.proxyName) + NETWORK_CONFIG_ERROR('Proxy', proxyConfig.name) ) }) @@ -316,7 +316,7 @@ describe('proxy configs tests', () => { const getByNameSpy = spyOn(proxyConfigsTable, 'getByName') getByNameSpy.mockResolvedValue(proxyConfig) await expect(proxyConfigsTable.update(proxyConfig)).rejects.toThrow(CONCURRENCY_MESSAGE) - expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.proxyName, proxyConfig.tenantId) + expect(getByNameSpy).toHaveBeenCalledWith(proxyConfig.name, proxyConfig.tenantId) expect(querySpy).toBeCalledTimes(1) expect(querySpy).toBeCalledWith( ` @@ -324,7 +324,7 @@ describe('proxy configs tests', () => { SET address=$2, info_format=$3, port=$4, network_dns_suffix=$5 WHERE proxy_config_name=$1 and tenant_id = $6`, [ - proxyConfig.proxyName, + proxyConfig.name, proxyConfig.address, proxyConfig.infoFormat, proxyConfig.port, diff --git a/src/data/postgres/tables/proxyConfigs.ts b/src/data/postgres/tables/proxyConfigs.ts index 2bc417512..cb1853689 100644 --- a/src/data/postgres/tables/proxyConfigs.ts +++ b/src/data/postgres/tables/proxyConfigs.ts @@ -57,7 +57,7 @@ export class ProxyConfigsTable implements IProxyConfigsTable { const results = await this.db.query( ` SELECT - proxy_config_name as "proxyName", + proxy_config_name as "name", address as "address", info_format as "infoFormat", port as "port", @@ -78,14 +78,14 @@ export class ProxyConfigsTable implements IProxyConfigsTable { /** * @description Get proxy profile from DB by name - * @param {string} proxyName + * @param {string} name * @returns {ProxyConfig} ProxyConfig object */ - async getByName(proxyName: string, tenantId = ''): Promise { + async getByName(name: string, tenantId = ''): Promise { const results = await this.db.query( ` SELECT - proxy_config_name as "proxyName", + proxy_config_name as "name", address as "address", info_format as "infoFormat", port as "port", @@ -93,7 +93,7 @@ export class ProxyConfigsTable implements IProxyConfigsTable { tenant_id as "tenantId" FROM proxyconfigs WHERE proxy_config_name = $1 and tenant_id = $2`, - [proxyName, tenantId] + [name, tenantId] ) if ((results?.rowCount ?? 0) > 0) { @@ -105,16 +105,16 @@ export class ProxyConfigsTable implements IProxyConfigsTable { /** * @description Check proxy profile exists in DB by name - * @param {string} proxyName + * @param {string} name * @returns {string[]} */ - async checkProfileExits(proxyName: string, tenantId = ''): Promise { + async checkProfileExits(name: string, tenantId = ''): Promise { const results = await this.db.query( ` SELECT 1 FROM proxyconfigs WHERE proxy_config_name = $1 and tenant_id = $2`, - [proxyName, tenantId] + [name, tenantId] ) if ((results?.rowCount ?? 0) > 0) { @@ -126,19 +126,19 @@ export class ProxyConfigsTable implements IProxyConfigsTable { /** * @description Delete proxy profile from DB by name - * @param {string} proxyName + * @param {string} name * @returns {boolean} Return true on successful deletion */ - async delete(proxyName: string, tenantId = ''): Promise { + async delete(name: string, tenantId = ''): Promise { const profiles = await this.db.query( ` SELECT 1 FROM profiles_proxyconfigs WHERE proxy_config_name = $1 and tenant_id = $2`, - [proxyName, tenantId] + [name, tenantId] ) if ((profiles?.rowCount ?? 0) > 0) { - throw new RPSError(NETWORK_CONFIG_DELETION_FAILED_CONSTRAINT('Proxy', proxyName), 'Foreign key violation') + throw new RPSError(NETWORK_CONFIG_DELETION_FAILED_CONSTRAINT('Proxy', name), 'Foreign key violation') } try { const results = await this.db.query( @@ -146,17 +146,17 @@ export class ProxyConfigsTable implements IProxyConfigsTable { DELETE FROM proxyconfigs WHERE proxy_config_name = $1 and tenant_id = $2`, - [proxyName, tenantId] + [name, tenantId] ) if (results?.rowCount) { return results.rowCount > 0 } } catch (error) { - this.log.error(`Failed to delete proxy configuration : ${proxyName}`, error) + this.log.error(`Failed to delete proxy configuration : ${name}`, error) if (error.code === PostgresErr.C23_FOREIGN_KEY_VIOLATION) { - throw new RPSError(NETWORK_CONFIG_DELETION_FAILED_CONSTRAINT('Proxy', proxyName)) + throw new RPSError(NETWORK_CONFIG_DELETION_FAILED_CONSTRAINT('Proxy', name)) } - throw new RPSError(API_UNEXPECTED_EXCEPTION(`Delete proxy configuration : ${proxyName}`)) + throw new RPSError(API_UNEXPECTED_EXCEPTION(`Delete proxy configuration : ${name}`)) } return false } @@ -175,7 +175,7 @@ export class ProxyConfigsTable implements IProxyConfigsTable { (proxy_config_name, address, info_format, port, network_dns_suffix, creation_date, tenant_id) values($1, $2, $3, $4, $5, $6, $7)`, [ - proxyConfig.proxyName, + proxyConfig.name, proxyConfig.address, proxyConfig.infoFormat, proxyConfig.port, @@ -186,17 +186,14 @@ export class ProxyConfigsTable implements IProxyConfigsTable { ) if ((results?.rowCount ?? 0) > 0) { - const config = await this.getByName(proxyConfig.proxyName, proxyConfig.tenantId) + const config = await this.getByName(proxyConfig.name, proxyConfig.tenantId) return config } } catch (error) { if (error.code === PostgresErr.C23_UNIQUE_VIOLATION) { - throw new RPSError( - NETWORK_CONFIG_INSERTION_FAILED_DUPLICATE('Proxy', proxyConfig.proxyName), - 'Unique key violation' - ) + throw new RPSError(NETWORK_CONFIG_INSERTION_FAILED_DUPLICATE('Proxy', proxyConfig.name), 'Unique key violation') } - throw new RPSError(NETWORK_CONFIG_ERROR('Proxy', proxyConfig.proxyName)) + throw new RPSError(NETWORK_CONFIG_ERROR('Proxy', proxyConfig.name)) } return null } @@ -216,7 +213,7 @@ export class ProxyConfigsTable implements IProxyConfigsTable { SET address=$2, info_format=$3, port=$4, network_dns_suffix=$5 WHERE proxy_config_name=$1 and tenant_id = $6`, [ - proxyConfig.proxyName, + proxyConfig.name, proxyConfig.address, proxyConfig.infoFormat, proxyConfig.port, @@ -225,12 +222,12 @@ export class ProxyConfigsTable implements IProxyConfigsTable { ] ) - latestItem = await this.getByName(proxyConfig.proxyName, proxyConfig.tenantId) + latestItem = await this.getByName(proxyConfig.name, proxyConfig.tenantId) if ((results?.rowCount ?? 0) > 0) { return latestItem } } catch (error) { - throw new RPSError(NETWORK_CONFIG_ERROR('Proxy', proxyConfig.proxyName)) + throw new RPSError(NETWORK_CONFIG_ERROR('Proxy', proxyConfig.name)) } // making assumption that if no records are updated, that it is due to concurrency. We've already checked for if it doesn't exist before calling update. throw new RPSError(CONCURRENCY_MESSAGE, CONCURRENCY_EXCEPTION, latestItem) diff --git a/src/models/RCS.Config.ts b/src/models/RCS.Config.ts index 9c687a206..3fe7a1b02 100644 --- a/src/models/RCS.Config.ts +++ b/src/models/RCS.Config.ts @@ -303,9 +303,9 @@ export interface connectionParams { } export interface ProxyConfig { - proxyName: string - address: string // A string holding the IP address or FQDN of the server - infoFormat: AMT.Types.MPServer.InfoFormat // An enumerated integer describing the format and interpretation of the address property (IPv4 (3), IPv6 (4), FQDN (201)) + name: string + address: string // IP address or FQDN of the server + infoFormat: AMT.Types.MPServer.InfoFormat // IPv4 (3), IPv6 (4), FQDN (201) port: number networkDnsSuffix: string // Domain name of the network to which this proxy belongs tenantId: string @@ -313,6 +313,6 @@ export interface ProxyConfig { export interface ProfileProxyConfigs { priority: number - profileName: string + name: string tenantId: string } diff --git a/src/routes/admin/profiles/amtProfileValidator.ts b/src/routes/admin/profiles/amtProfileValidator.ts index 14198f591..5e5e135d6 100644 --- a/src/routes/admin/profiles/amtProfileValidator.ts +++ b/src/routes/admin/profiles/amtProfileValidator.ts @@ -201,9 +201,9 @@ const validatewifiConfigs = async (value: any, req: Request): Promise const validateProxyConfigs = async (value: any, req: Request): Promise => { const proxyConfigNames: string[] = [] for (const config of value) { - const isProxyExist = await req.db.proxyConfigs.checkProfileExits(config.profileName, req.tenantId) + const isProxyExist = await req.db.proxyConfigs.checkProfileExits(config.name, req.tenantId) if (!isProxyExist) { - proxyConfigNames.push(config.profileName) + proxyConfigNames.push(config.name) } } return proxyConfigNames diff --git a/src/routes/admin/profiles/edit.test.ts b/src/routes/admin/profiles/edit.test.ts index f59bf3e82..b4cb11b62 100644 --- a/src/routes/admin/profiles/edit.test.ts +++ b/src/routes/admin/profiles/edit.test.ts @@ -12,7 +12,8 @@ import { handleGenerateRandomMEBxPassword, handleGenerateRandomPassword, handleMEBxPassword, - handleWifiConfigs + handleWifiConfigs, + handleProxyConfigs } from './edit.js' import { ClientAction, TlsMode, TlsSigningAuthority } from '../../../models/RCS.Config.js' import { jest } from '@jest/globals' @@ -32,13 +33,19 @@ describe('AMT Profile - Edit', () => { 'send' ]) req = { - db: { profiles: { getByName: jest.fn(), update: jest.fn() } }, + db: { + profiles: { getByName: jest.fn(), update: jest.fn() }, + profileWirelessConfigs: { deleteProfileWifiConfigs: jest.fn() }, + profileProxyConfigs: { deleteProfileProxyConfigs: jest.fn() } + }, body: { profileName: 'profileName' }, query: {}, tenantId: '' } getByNameSpy = jest.spyOn(req.db.profiles, 'getByName').mockResolvedValue({}) jest.spyOn(req.db.profiles, 'update').mockResolvedValue({}) + jest.spyOn(req.db.profileWirelessConfigs, 'deleteProfileWifiConfigs').mockResolvedValue(true) + jest.spyOn(req.db.profileProxyConfigs, 'deleteProfileProxyConfigs').mockResolvedValue(true) resSpy.status.mockReturnThis() resSpy.json.mockReturnThis() diff --git a/src/routes/admin/profiles/edit.ts b/src/routes/admin/profiles/edit.ts index fb81bf18a..e4c02e81f 100644 --- a/src/routes/admin/profiles/edit.ts +++ b/src/routes/admin/profiles/edit.ts @@ -6,10 +6,16 @@ import Logger from '../../../Logger.js' import { NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js' import { type AMTConfiguration } from '../../../models/index.js' -import { ClientAction, type ProfileWifiConfigs, TlsSigningAuthority } from '../../../models/RCS.Config.js' +import { + ClientAction, + type ProfileWifiConfigs, + type ProfileProxyConfigs, + TlsSigningAuthority +} from '../../../models/RCS.Config.js' import { MqttProvider } from '../../../utils/MqttProvider.js' import { type Request, type Response } from 'express' import { type IProfilesWifiConfigsTable } from '../../../interfaces/database/IProfileWifiConfigsDb.js' +import { type IProfileProxyConfigsTable } from '../../../interfaces/database/IProfileProxyConfigsDb.js' import handleError from '../../../utils/handleError.js' import { RPSError } from '../../../utils/RPSError.js' import { type DeviceCredentials } from '../../../interfaces/ISecretManagerService.js' @@ -27,6 +33,7 @@ export async function editProfile(req: Request, res: Response): Promise { } else { let amtConfig: AMTConfiguration = await getUpdatedData(newConfig, oldConfig) amtConfig.wifiConfigs = await handleWifiConfigs(newConfig, oldConfig, req.db.profileWirelessConfigs) + amtConfig.proxyConfigs = await handleProxyConfigs(newConfig, oldConfig, req.db.profileProxyConfigs) // Assigning value key value for AMT Random Password and MEBx Random Password to store in database const amtPwdBefore = amtConfig.amtPassword ?? '' const mebxPwdBefore = amtConfig.mebxPassword ?? '' @@ -161,6 +168,18 @@ export const handleWifiConfigs = async ( return wifiConfigs } +export const handleProxyConfigs = async ( + newConfig: AMTConfiguration, + oldConfig: AMTConfiguration, + profileProxyConfigsDb: IProfileProxyConfigsTable +): Promise => { + let proxyConfigs: ProfileProxyConfigs[] | null = null + // Always delete existing proxy configs and set new ones if provided + await profileProxyConfigsDb.deleteProfileProxyConfigs(newConfig.profileName, newConfig.tenantId) + proxyConfigs = newConfig.proxyConfigs ?? null + return proxyConfigs +} + export const getUpdatedData = async (newConfig: any, oldConfig: AMTConfiguration): Promise => { let amtConfig: AMTConfiguration = { profileName: newConfig.profileName } as AMTConfiguration amtConfig = handleAMTPassword(amtConfig, newConfig, oldConfig) diff --git a/src/routes/admin/proxy/create.test.ts b/src/routes/admin/proxy/create.test.ts index e59dd3393..ec81b3574 100644 --- a/src/routes/admin/proxy/create.test.ts +++ b/src/routes/admin/proxy/create.test.ts @@ -22,7 +22,9 @@ describe('Proxy - Create', () => { ]) req = { db: { proxyConfigs: { insert: jest.fn() } }, - body: {}, + body: { + address: '192.168.1.1' // IPv4 address for testing auto-detection + }, query: {} } insertSpy = spyOn(req.db.proxyConfigs, 'insert').mockResolvedValue({}) @@ -30,9 +32,28 @@ describe('Proxy - Create', () => { resSpy.json.mockReturnThis() resSpy.send.mockReturnThis() }) - it('should create', async () => { + it('should create and auto-detect infoFormat for IPv4', async () => { + req.body.address = '192.168.1.1' await createProxyProfile(req, resSpy) expect(insertSpy).toHaveBeenCalledTimes(1) + // Verify that infoFormat was set to 3 (IPv4) + expect(req.body.infoFormat).toBe(3) + expect(resSpy.status).toHaveBeenCalledWith(201) + }) + it('should create and auto-detect infoFormat for IPv6', async () => { + req.body.address = '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + await createProxyProfile(req, resSpy) + expect(insertSpy).toHaveBeenCalledTimes(1) + // Verify that infoFormat was set to 4 (IPv6) + expect(req.body.infoFormat).toBe(4) + expect(resSpy.status).toHaveBeenCalledWith(201) + }) + it('should create and auto-detect infoFormat for FQDN', async () => { + req.body.address = 'proxy.example.com' + await createProxyProfile(req, resSpy) + expect(insertSpy).toHaveBeenCalledTimes(1) + // Verify that infoFormat was set to 201 (FQDN) + expect(req.body.infoFormat).toBe(201) expect(resSpy.status).toHaveBeenCalledWith(201) }) it('should handle error', async () => { diff --git a/src/routes/admin/proxy/create.ts b/src/routes/admin/proxy/create.ts index a6bc7109d..fb5385f53 100644 --- a/src/routes/admin/proxy/create.ts +++ b/src/routes/admin/proxy/create.ts @@ -8,16 +8,21 @@ import Logger from '../../../Logger.js' import { MqttProvider } from '../../../utils/MqttProvider.js' import { type Request, type Response } from 'express' import handleError from '../../../utils/handleError.js' +import { detectAddressFormat } from './proxyValidator.js' + export async function createProxyProfile(req: Request, res: Response): Promise { const proxyConfig: ProxyConfig = req.body proxyConfig.tenantId = req.tenantId || '' const log = new Logger('createProxyProfile') try { + // Auto-detect the infoFormat based on the address + proxyConfig.infoFormat = detectAddressFormat(proxyConfig.address) + const results: ProxyConfig | null = await req.db.proxyConfigs.insert(proxyConfig) - log.verbose(`Created proxy profile: ${proxyConfig.proxyName}`) - MqttProvider.publishEvent('success', ['createProxyConfigs'], `Created proxy profile: ${proxyConfig.proxyName}`) + log.verbose(`Created proxy profile: ${proxyConfig.name}`) + MqttProvider.publishEvent('success', ['createProxyConfigs'], `Created proxy profile: ${proxyConfig.name}`) res.status(201).json(results).end() } catch (error) { - handleError(log, 'proxyConfig.proxyName', req, res, error) + handleError(log, 'proxyConfig.name', req, res, error) } } diff --git a/src/routes/admin/proxy/delete.test.ts b/src/routes/admin/proxy/delete.test.ts index 97e2e7bdf..aa547b4f7 100644 --- a/src/routes/admin/proxy/delete.test.ts +++ b/src/routes/admin/proxy/delete.test.ts @@ -24,7 +24,7 @@ describe('Proxy - Delete', () => { req = { db: { proxyConfigs: { delete: jest.fn(), checkProfileExits: jest.fn() } }, query: {}, - params: { proxyName: 'proxyConfigName' }, + params: { name: 'proxyConfigName' }, tenantId: '', method: 'DELETE' } diff --git a/src/routes/admin/proxy/delete.ts b/src/routes/admin/proxy/delete.ts index d869313ce..7d0f56be9 100644 --- a/src/routes/admin/proxy/delete.ts +++ b/src/routes/admin/proxy/delete.ts @@ -11,18 +11,18 @@ import { RPSError } from '../../../utils/RPSError.js' import { API_UNEXPECTED_EXCEPTION, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js' export async function deleteProxyProfile(req: Request, res: Response): Promise { - const { proxyName } = req.params + const { name } = req.params const tenantId = req.tenantId || '' const log = new Logger('deleteProxyProfile') try { - const proxyConfigExists: boolean = await req.db.proxyConfigs.checkProfileExits(proxyName, tenantId) + const proxyConfigExists: boolean = await req.db.proxyConfigs.checkProfileExits(name, tenantId) if (!proxyConfigExists) { - throw new RPSError(NOT_FOUND_MESSAGE('Proxy', proxyName), NOT_FOUND_EXCEPTION) + throw new RPSError(NOT_FOUND_MESSAGE('Proxy', name), NOT_FOUND_EXCEPTION) } else { - const results: boolean | null = await req.db.proxyConfigs.delete(proxyName, tenantId) + const results: boolean | null = await req.db.proxyConfigs.delete(name, tenantId) if (results) { - log.verbose(`Deleted proxy profile : ${proxyName}`) - MqttProvider.publishEvent('success', ['deleteProxyProfile'], `Deleted proxy configuration : ${proxyName}`) + log.verbose(`Deleted proxy profile : ${name}`) + MqttProvider.publishEvent('success', ['deleteProxyProfile'], `Deleted proxy configuration : ${name}`) res.status(204).json(results).end() } else { throw new RPSError(API_UNEXPECTED_EXCEPTION('Error deleting proxy configuration')) diff --git a/src/routes/admin/proxy/edit.test.ts b/src/routes/admin/proxy/edit.test.ts index bc63d81ce..1001f7a8d 100644 --- a/src/routes/admin/proxy/edit.test.ts +++ b/src/routes/admin/proxy/edit.test.ts @@ -28,8 +28,7 @@ describe('Proxy - Edit', () => { tenantId: '', method: 'PATCH', body: { - address: 'intel.com', - infoFormat: 201, + address: 'intel.com', // FQDN for testing auto-detection (will be 201) networkDnsSuffix: 'vprodemo', port: 443, tenantId: 'foo' diff --git a/src/routes/admin/proxy/edit.ts b/src/routes/admin/proxy/edit.ts index 8281ddf26..a883e7d17 100644 --- a/src/routes/admin/proxy/edit.ts +++ b/src/routes/admin/proxy/edit.ts @@ -10,21 +10,22 @@ import handleError from '../../../utils/handleError.js' import { RPSError } from '../../../utils/RPSError.js' import { API_UNEXPECTED_EXCEPTION, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js' import { ProxyConfig } from 'models/RCS.Config.js' +import { detectAddressFormat } from './proxyValidator.js' export async function editProxyProfile(req: Request, res: Response): Promise { const newProxy: ProxyConfig = req.body newProxy.tenantId = req.tenantId || '' const log = new Logger('editProxyProfile') try { - const oldProxy: ProxyConfig | null = await req.db.proxyConfigs.getByName(newProxy.proxyName, req.tenantId) + const oldProxy: ProxyConfig | null = await req.db.proxyConfigs.getByName(newProxy.name, req.tenantId) if (oldProxy == null) { - throw new RPSError(NOT_FOUND_MESSAGE('Proxy', newProxy.proxyName), NOT_FOUND_EXCEPTION) + throw new RPSError(NOT_FOUND_MESSAGE('Proxy', newProxy.name), NOT_FOUND_EXCEPTION) } else { const proxiConfig: ProxyConfig = await getUpdatedData(newProxy, oldProxy) const results = await req.db.proxyConfigs.update(proxiConfig) if (results) { - MqttProvider.publishEvent('success', ['editProxyConfig'], `Updated proxy configuration : ${newProxy.proxyName}`) + MqttProvider.publishEvent('success', ['editProxyConfig'], `Updated proxy configuration : ${newProxy.name}`) res.status(200).json(results).end() } else { throw new RPSError(API_UNEXPECTED_EXCEPTION('Error updating proxy configuration')) @@ -36,9 +37,13 @@ export async function editProxyProfile(req: Request, res: Response): Promise => { - const proxyConfig: ProxyConfig = { proxyName: newProxy.proxyName } as ProxyConfig - proxyConfig.address = newProxy.address ?? oldProxy.address - proxyConfig.infoFormat = newProxy.infoFormat ?? oldProxy.infoFormat + const proxyConfig: ProxyConfig = { name: newProxy.name } as ProxyConfig + // Determine the final address - either new or old + const finalAddress = newProxy.address ?? oldProxy.address + + proxyConfig.address = finalAddress + // Auto-detect infoFormat based on the final address + proxyConfig.infoFormat = detectAddressFormat(finalAddress) proxyConfig.networkDnsSuffix = newProxy.networkDnsSuffix ?? oldProxy.networkDnsSuffix proxyConfig.port = newProxy.port ?? oldProxy.port proxyConfig.tenantId = oldProxy.tenantId diff --git a/src/routes/admin/proxy/get.test.ts b/src/routes/admin/proxy/get.test.ts index 7536be394..1382e54be 100644 --- a/src/routes/admin/proxy/get.test.ts +++ b/src/routes/admin/proxy/get.test.ts @@ -26,14 +26,14 @@ describe('Proxy - Get', () => { req = { db: { proxyConfigs: { getByName: jest.fn() } }, query: {}, - params: { proxyName: 'proxyConfigName' }, + params: { name: 'proxyConfigName' }, tenantId: '', method: 'GET' } getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue( (proxyConfig = { - proxyName: 'proxyConfigName', + name: 'proxyConfigName', address: 'intel.com', infoFormat: 201, networkDnsSuffix: 'vprodemo', diff --git a/src/routes/admin/proxy/get.ts b/src/routes/admin/proxy/get.ts index 75b9f7aa6..ecec28339 100644 --- a/src/routes/admin/proxy/get.ts +++ b/src/routes/admin/proxy/get.ts @@ -7,15 +7,15 @@ import { API_RESPONSE, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../u import { ProxyConfig } from 'models/RCS.Config.js' export async function getProxyProfile(req: Request, res: Response) { - const { proxyName } = req.params + const { name } = req.params const tenantId = req.tenantId || '' const log = new Logger('getProxyProfile') try { - const result: ProxyConfig | null = await req.db.proxyConfigs.getByName(proxyName, tenantId) + const result: ProxyConfig | null = await req.db.proxyConfigs.getByName(name, tenantId) if (result == null) { - throw new RPSError(NOT_FOUND_MESSAGE('Proxy', proxyName), NOT_FOUND_EXCEPTION) + throw new RPSError(NOT_FOUND_MESSAGE('Proxy', name), NOT_FOUND_EXCEPTION) } else { - MqttProvider.publishEvent('success', ['getProxyProfile'], `Sent Profile : ${proxyName}`) + MqttProvider.publishEvent('success', ['getProxyProfile'], `Sent Profile : ${name}`) res.status(200).json(API_RESPONSE(result)).end() } } catch (error) { diff --git a/src/routes/admin/proxy/index.ts b/src/routes/admin/proxy/index.ts index 0b41bf45e..2a5c2556a 100644 --- a/src/routes/admin/proxy/index.ts +++ b/src/routes/admin/proxy/index.ts @@ -16,8 +16,8 @@ import { editProxyProfile } from './edit.js' const proxyRouter: Router = Router() proxyRouter.get('/', odataValidator(), validateMiddleware, allProxyProfiles) -proxyRouter.get('/:proxyName', getProxyProfile) +proxyRouter.get('/:name', getProxyProfile) proxyRouter.post('/', proxyValidator(), validateMiddleware, createProxyProfile) proxyRouter.patch('/', proxyUpdateValidator(), validateMiddleware, editProxyProfile) -proxyRouter.delete('/:proxyName', deleteProxyProfile) +proxyRouter.delete('/:name', deleteProxyProfile) export default proxyRouter diff --git a/src/routes/admin/proxy/proxyValidator.ts b/src/routes/admin/proxy/proxyValidator.ts index 1a66dc4d6..90a048e36 100644 --- a/src/routes/admin/proxy/proxyValidator.ts +++ b/src/routes/admin/proxy/proxyValidator.ts @@ -4,9 +4,33 @@ **********************************************************************/ import { check } from 'express-validator' +import { type AMT } from '@device-management-toolkit/wsman-messages' + +/** + * Detects the address format (IPv4, IPv6, or FQDN) based on the address string + * @param address The address to detect + * @returns InfoFormat enum value (3 for IPv4, 4 for IPv6, 201 for FQDN) + */ +export function detectAddressFormat(address: string): AMT.Types.MPServer.InfoFormat { + // Check for IPv6 (contains colons and hex characters) + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/ + if (ipv6Regex.test(address)) { + return 4 // IPv6 + } + + // Check for IPv4 (xxx.xxx.xxx.xxx) + const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/ + if (ipv4Regex.test(address)) { + return 3 // IPv4 + } + + // Otherwise, treat as FQDN + return 201 // FQDN +} export const proxyValidator = (): any => [ - check('proxyName') + check('name') .not() .isEmpty() .withMessage('Proxy profile name is required') @@ -15,36 +39,23 @@ export const proxyValidator = (): any => [ .isLength({ max: 32 }) .withMessage('Proxy profile name maximum length is 32'), - // Validate and normalize infoFormat first so conditional checks can rely on a number - check('infoFormat') + // address presence and format validation + check('address') .not() .isEmpty() - .withMessage('Server address format is required') - .isInt() - .toInt() - .isIn([ - 3, - 4, - 201 - ]) - .withMessage('Server address format should be either 3(IPV4), 4(IPV6) or 201(FQDN)'), + .withMessage('Server address is required') + .custom((value) => { + // Validate if it's a valid IPv4, IPv6, or FQDN + const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/ + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/ + const fqdnRegex = /^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\.[a-zA-Z]{2,11}?$/ - // address presence - check('address').not().isEmpty().withMessage('Server address is required'), - - // address format based on infoFormat - check('address') - .if((_, { req }) => req.body.infoFormat === 3) - .isIP(4) - .withMessage('infoFormat 3 requires IPV4 server address'), - check('address') - .if((_, { req }) => req.body.infoFormat === 4) - .isIP(6) - .withMessage('infoFormat 4 requires IPV6 server address'), - check('address') - .if((_, { req }) => req.body.infoFormat === 201) - .isFQDN({ require_tld: true, allow_underscores: false, allow_numeric_tld: false }) - .withMessage('infoFormat 201 requires FQDN server address'), + if (ipv4Regex.test(value) || ipv6Regex.test(value) || fqdnRegex.test(value)) { + return true + } + throw new Error('Server address must be a valid IPv4, IPv6, or FQDN') + }), check('port').exists().isPort().withMessage('Port value should range between 1 and 65535'), check('networkDnsSuffix') @@ -58,36 +69,23 @@ export const proxyValidator = (): any => [ ] export const proxyUpdateValidator = (): any => [ - // Validate and normalize infoFormat first so conditional checks can rely on a number - check('infoFormat') + // address presence and format validation + check('address') .not() .isEmpty() - .withMessage('Server address format is required') - .isInt() - .toInt() - .isIn([ - 3, - 4, - 201 - ]) - .withMessage('Server address format should be either 3(IPV4), 4(IPV6) or 201(FQDN)'), - - // address presence - check('address').not().isEmpty().withMessage('Server address is required'), + .withMessage('Server address is required') + .custom((value) => { + // Validate if it's a valid IPv4, IPv6, or FQDN + const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/ + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/ + const fqdnRegex = /^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\.[a-zA-Z]{2,11}?$/ - // address format based on infoFormat - check('address') - .if((_, { req }) => req.body.infoFormat === 3) - .isIP(4) - .withMessage('infoFormat 3 requires IPV4 server address'), - check('address') - .if((_, { req }) => req.body.infoFormat === 4) - .isIP(6) - .withMessage('infoFormat 4 requires IPV6 server address'), - check('address') - .if((_, { req }) => req.body.infoFormat === 201) - .isFQDN({ require_tld: true, allow_underscores: false, allow_numeric_tld: false }) - .withMessage('infoFormat 201 requires FQDN server address'), + if (ipv4Regex.test(value) || ipv6Regex.test(value) || fqdnRegex.test(value)) { + return true + } + throw new Error('Server address must be a valid IPv4, IPv6, or FQDN') + }), check('port').exists().isPort().withMessage('Port value should range between 1 and 65535'), check('networkDnsSuffix') diff --git a/src/stateMachines/networkConfiguration.ts b/src/stateMachines/networkConfiguration.ts index 0323b2323..56f655885 100644 --- a/src/stateMachines/networkConfiguration.ts +++ b/src/stateMachines/networkConfiguration.ts @@ -15,6 +15,7 @@ import { type CommonContext, invokeWsmanCall } from './common.js' import { UNEXPECTED_PARSE_ERROR } from '../utils/constants.js' import { WiredConfiguration } from './wiredNetworkConfiguration.js' import { WiFiConfiguration } from './wifiNetworkConfiguration.js' +import { ProxyConfiguration } from './proxyConfiguration.js' export interface NetworkConfigContext extends CommonContext { amtProfile: AMTConfiguration | null @@ -40,6 +41,7 @@ export class NetworkConfiguration { error: Error = new Error() wiredConfiguration: WiredConfiguration = new WiredConfiguration() wifiConfiguration: WiFiConfiguration = new WiFiConfiguration() + proxyConfiguration: ProxyConfiguration = new ProxyConfiguration() putGeneralSettings = async ({ input }: { input: NetworkConfigContext }): Promise => { input.xmlMessage = input.amt.GeneralSettings.Put(input.generalSettings) @@ -103,6 +105,7 @@ export class NetworkConfiguration { actors: { wiredConfiguration: this.wiredConfiguration.machine, wifiConfiguration: this.wifiConfiguration.machine, + proxyConfiguration: this.proxyConfiguration.machine, errorMachine: this.error.machine, putGeneralSettings: fromPromise(this.putGeneralSettings), enumerateEthernetPortSettings: fromPromise(this.enumerateEthernetPortSettings), @@ -124,6 +127,13 @@ export class NetworkConfiguration { } return false }, + isProxyGiven: ({ context }) => { + const profile = context.amtProfile + if (profile?.proxyConfigs != null) { + return profile.proxyConfigs.length > 0 + } + return false + }, isLocalProfileSynchronizationNotEnabled: ({ context }) => context.message.Envelope.Body.AMT_WiFiPortConfigurationService.localProfileSynchronizationEnabled === 0, shouldRetry: ({ context, event }) => @@ -141,6 +151,7 @@ export class NetworkConfiguration { } } }).createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QDswBcDuB7ATgawFoBjLZAMwEsoBXHAQzQtIIFs6iALC1AOgEEAwgBUAkgDU+ogPIA5AMQyAokIDqUgEoBpAbIBiIgOIBVdZJGyA2gAYAuolAAHLLAqNS9kAA9EAZgAsAEw8AKx+AIwAbBEA7AAcPgHRYWHRADQgAJ6IflYAnDzRVvE+PrkRfsGx0YkAvjXpqJi4hCTkVLQMTMis7Fy8AgASigKaAPoGikqmADKjAMrKojIGc3LWdkggTi5uyB7eCLE5Ibk+0cE+Vn65yT7B6VkIl8E8edUBAUc5RbF1DejYfDEUiUGj0XY9TjcMA8QbDMYTKZ8WYLIRLFZrMIbRzOVxdfaII5WE5nC5XG5hO4PbIRMIhYr+MLxKIVHx-ECNQEtEHtcFdSF9GEABSMQnGk0UM3mixEy1WEFIMO4ADcsHgYQ5qGgCDBUPQADYEWDoRjIKCwdYebZ49ybA5hYJXAqlSqxKqJHJhakIAK5F6OqyBqyVJllcrsznNYFtMGdZhsKG8EVixGS5HStGyjFgHA4XA8Bz6hhkXAsAtanVgPV0Q3GtCm82WzbW3YEhAOp3RF1u93RT3egJWOk3KJRXJ5PwRXJuiMAqOtUEdCEJwU8SZGACyaaEilGyiG6iUYqFGjFqPR8sVPBVaphVeoLBzDDABHQHBzjQIThw2rrDYtthWrirZ2ogDrxCEwSDrEuSDlYPgpN6lTEkUJT+AEoQJP4s5NECC68nG3QrtCa4yJu267vukpHqMJ7qGeMpynIOZ5jgBZFmgJY4GW96PuCL5vh+6Bfrgv4mtwjaAc2wH4qBPoIbEPDTn6NzTlOiQ+EhESKaOUTnH4fg+BERQ4Vy0aLny8a9CRIrTLMVGHsotGnhmF5yAqvA3uq5b6oagk4J+35ifWEkAdiWwybaoD2o6fjOn6PbVH2Q7esksTDpEURVAkwZ+KZ848rGy7WUmRh2XuQgHjRdEMZmTEsfmhbFqWPl+Wg74BcJQVGuJZphUBOyydFYGxfFrq9v2mSIDEPg8LpMSwTEjoBPleGFUu-LEaV5UOdVLnnlmqwNWxTWcS1mq+a+7VCdq3V-qFFhYgNNp7HJHZxV2CVuklk2PNEMQ8BhQYOrkNz-bkq3cjGG1WYmMJwiMFVVU5NWuYdtFlbM6iKHMJ4yAsaxSTig1RV4iABApSkwcEqmxOpXapVY5xzUDViDqUFMRMEkPmQRxVw7CQyI7tKP7YxKwY+V2O47IBOPeFLZDWT8lMlTKlhGpsEM1N7YBMchTFME5SXLEAQRDz+FFZtJXw0LYwi8eYt1RLtlYzjeNywECuRa9w0q4pyk0xrdNa5pOvjvkoSZUcdylPBFvrZZRE2zwKgiNjAAiow6DI+jGKY0jyB5SrIKq3kYBQOCQAQkZrdDScCtCTbEy9baXBhrwRBhtw00k5yM2UIRTgZXOFF2FQJ-XhGN7waeZ9neiGCYZiyO5V5eTCFdVxANdznXFnT1tYDy89IF++3LxWF3wQ97kff3DrYTkp3uQj4ECkfJPB-86uaf6Avucl4F3MEXdepdbw8ArpQXeuEobf2tnDZuEUSa+2VqUY48Ewhm2uOcKIXpH4BBSDwQywZgjRHIYOSIvx6gcj3nAvmCDVxCnUFIAAGgATQAXnZehc16eXAd5BweZPAZF5lbWGgokGK1JgcdBKEELYLvkbWk3o+yzQNiUPwpsjhm2of8WBYiYbJwFpKFh6g+ElzLneXMuAZ7HyJsg1ucl0IhHCHpeIiRkhpB1kZII0E0LpUKP4PRtCDGWyMXYtc6gzFyD0HwEQ0xFAZykT7NugRXGZTiAkJIiEdbpR4JlUcptX40wwnUGhyAsAQDgB4Wu9DxHGMFKfJWBwCARG9AQF4oNQbuhjoEcIX8GESJIoIUQEhC7NJkeTeCxCr5AxiEUKcVRvSTmJPNc4TNyg00GQ0yJCMEQSilAdOUkzUEHBpl0yoJRQhaPIXkFZFxiGFL7LEN4yidkRKPjwZM4okQonFnMU5bZkgzLNl2LmkQNZ+G1o8T4KFKRulflcfwTMPkNy+euLcBdKKVWoqLeiaMTnSRQcCmCEQChhAqOOIycy-RITITwKoAZUKbIdGiw+KdXZIzxY7AlxyVhAreqhMa2U8inAdKlB0QQrnXMSN9c47Kf4kX2dyxyvLaoXklm7GW+NFCCr9qGWa1MiT+GhWQsOjxkivx4Gha5BlTY30VYwkic8klcKASvGQ+rlZm19J3IGd8yjjmWY-NmikrjR0dMZa4TrhmzxEP-HO3DgGyG9bIm5rwFEREZJ8KCARJWFBOO8fpULtKxsaTZFhHD3X509Wm3wGbMFmxzbEPN3opzkqZYGLB047jRHLZE0xGh60IECN6S45L-DvD1kkFkKQB1fN0PExJGcR2+vyHMx0gapxFG8Y8YIRtGUyrcVCpIC6U5zCMAIAQONAXEqcX7QIXcCjRoSPEU20LGbpMIa-Sc6CcnlJqEAA */ // todo: the actual context comes in from the parent and clobbers this one // xstate version 5 should fix this. context: ({ input }) => ({ @@ -270,6 +281,10 @@ export class NetworkConfiguration { guard: 'isWifiSupportedOnDevice', target: 'WIFI_CONFIGURATION' }, + { + guard: 'isProxyGiven', + target: 'PROXY_CONFIGURATION' + }, { target: 'SUCCESS' } ] } @@ -290,6 +305,29 @@ export class NetworkConfiguration { amt: context.amt, cim: context.cim }), + onDone: [ + { + guard: 'isProxyGiven', + target: 'PROXY_CONFIGURATION' + }, + { target: 'SUCCESS' } + ] + } + }, + PROXY_CONFIGURATION: { + entry: sendTo('proxy-configuration-machine', { type: 'PROXYCONFIG' }), + invoke: { + src: 'proxyConfiguration', + id: 'proxy-configuration-machine', + input: ({ context }) => ({ + clientId: context.clientId, + amtProfile: context.amtProfile, + httpHandler: context.httpHandler, + message: '', + proxyConfigsCount: 0, + retryCount: 0, + ips: context.ips + }), onDone: 'SUCCESS' } }, diff --git a/src/stateMachines/proxyConfiguration.test.ts b/src/stateMachines/proxyConfiguration.test.ts new file mode 100644 index 000000000..2ef575a6b --- /dev/null +++ b/src/stateMachines/proxyConfiguration.test.ts @@ -0,0 +1,273 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +import { randomUUID } from 'node:crypto' +import { devices } from '../devices.js' +import { Environment } from '../utils/Environment.js' +import { config } from '../test/helper/Config.js' +import { ClientAction } from '../models/RCS.Config.js' +import { + type ProxyConfigContext, + type ProxyConfigEvent, + type ProxyConfiguration as ProxyConfigurationType +} from './proxyConfiguration.js' +import { type MachineImplementationsSimplified, createActor, fromPromise } from 'xstate' +import { HttpHandler } from '../HttpHandler.js' +import { IPS } from '@device-management-toolkit/wsman-messages' +import { jest } from '@jest/globals' + +const { ProxyConfiguration } = await import('./proxyConfiguration.js') +const clientId = randomUUID() +Environment.Config = config + +describe('Proxy Configuration State Machine', () => { + let config: MachineImplementationsSimplified + let proxyConfiguration: ProxyConfigurationType + let context + let currentStateIndex: number + + beforeEach(() => { + proxyConfiguration = new ProxyConfiguration() + context = { + amtProfile: { + profileName: 'acm', + generateRandomPassword: false, + activation: ClientAction.ADMINCTLMODE, + ciraConfigName: 'config1', + generateRandomMEBxPassword: false, + tags: ['acm'], + dhcpEnabled: true, + ipSyncEnabled: true, + localWifiSyncEnabled: true, + tenantId: 'tenant1', + proxyConfigs: [ + { + priority: 1, + name: 'proxy1' + } + ] + }, + proxyConfigsCount: 0, + retryCount: 0, + clientId, + httpHandler: new HttpHandler(), + ips: new IPS.Messages() + } + devices[clientId] = { + status: { Network: 'Initial' } + } as any + + currentStateIndex = 0 + config = { + actors: { + getProxyConfig: fromPromise(async ({ input }) => await Promise.resolve({ clientId })), + addProxyConfigs: fromPromise(async () => ({ + Envelope: { Body: { AddProxyAccessPoint_OUTPUT: { ReturnValue: 0 } } } + })), + errorMachine: fromPromise(async ({ input }) => ({ clientId: input.clientId })) + }, + guards: { + isMoreProxyConfigs: () => false, + isProxyConfigsExist: () => true, + shouldRetry: () => false + }, + actions: {}, + delays: {} + } + }) + + describe('State machines', () => { + it('should reach FAILED state if getProxyConfig throws', (done) => { + config.actors!.getProxyConfig = fromPromise(async ({ input }) => await Promise.reject(new Error())) + const machine = proxyConfiguration.machine.provide(config) + const flowStates = [ + 'ACTIVATION', + 'GET_PROXY_CONFIG', + 'FAILED' + ] + const service = createActor(machine, { input: context }) + + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('FAILED') && currentStateIndex === flowStates.length) { + const status = devices[clientId].status.Network + expect(status).toContain('Failed to get proxy config from DB') + service.stop() + done() + } + }) + service.start() + service.send({ type: 'PROXYCONFIG', clientId }) + }) + + it('should add a Proxy config to AMT.', (done) => { + context.proxyConfig = { + proxyName: 'proxy1', + address: 'www.vprodemo.com', + infoFormat: 201, + port: 900, + networkDnsSuffix: 'intel.com' + } + context.proxyConfigName = 'proxy1' + context.proxyConfigsCount = 1 + config.guards = { + isMoreProxyConfigs: () => false, + isProxyConfigsExist: () => true + } + + const machine = proxyConfiguration.machine.provide(config) + const flowStates = [ + 'ACTIVATION', + 'GET_PROXY_CONFIG', + 'ADD_PROXY_CONFIGS', + 'SUCCESS' + ] + const service = createActor(machine, { input: context }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('SUCCESS') && currentStateIndex === flowStates.length) { + const status = devices[clientId].status.Network + expect(status).toEqual('Initial. Proxy Configured') + service.stop() + done() + } + }) + service.start() + service.send({ type: 'PROXYCONFIG', clientId }) + }) + + it('should fail and report the detail message with added and failed configs.', (done) => { + context.proxyConfig = { + proxyName: 'proxy1', + address: 'www.vprodemo.com', + infoFormat: 201, + port: 900, + networkDnsSuffix: 'intel.com' + } + config.actors!.addProxyConfigs = fromPromise(async ({ input }) => await Promise.reject(new Error())) + context.proxyConfigName = 'proxy1' + context.proxyConfigsCount = 1 + config.guards = { + isMoreProxyConfigs: () => false, + isProxyConfigsExist: () => true + } + + const mockNetworkConfigurationMachine = proxyConfiguration.machine.provide(config) + const flowStates = [ + 'ACTIVATION', + 'GET_PROXY_CONFIG', + 'ADD_PROXY_CONFIGS', + 'SUCCESS' + ] + const service = createActor(mockNetworkConfigurationMachine, { input: context }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('SUCCESS') && currentStateIndex === flowStates.length) { + const status = devices[clientId].status.Network + expect(status).toEqual('Initial. Failed to add proxy1') + done() + } + }) + service.start() + service.send({ type: 'PROXYCONFIG', clientId }) + }) + + it('should fail and report the detail message with added and return value 1.', (done) => { + config.actors!.addProxyConfigs = fromPromise( + async ({ input }) => + await Promise.resolve({ Envelope: { Body: { AddProxyAccessPoint_OUTPUT: { ReturnValue: 1 } } } }) + ) + context.proxyConfigName = 'proxy1' + context.proxyConfigsCount = 1 + config.guards = { + isMoreProxyConfigs: () => false, + isProxyConfigsExist: () => true + } + + const mockNetworkConfigurationMachine = proxyConfiguration.machine.provide(config) + const flowStates = [ + 'ACTIVATION', + 'GET_PROXY_CONFIG', + 'ADD_PROXY_CONFIGS', + 'SUCCESS' + ] + const service = createActor(mockNetworkConfigurationMachine, { input: context }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('SUCCESS') && currentStateIndex === flowStates.length) { + const status = devices[clientId].status.Network + expect(status).toEqual('Initial. Failed to add proxy1') + done() + } + }) + service.start() + service.send({ type: 'PROXYCONFIG', clientId }) + }) + + it('should fail and report the detail message with added', (done) => { + config.actors!.addProxyConfigs = fromPromise( + async ({ input }) => + await Promise.resolve({ Envelope: { Body: { AddProxyAccessPoint_OUTPUT: { ReturnValue: 0 } } } }) + ) + context.proxyConfigsAdded = 'proxy1' + context.proxyConfigName = 'proxy2' + context.proxyConfigsCount = 1 + config.guards = { + isMoreProxyConfigs: () => false, + isProxyConfigsExist: () => true + } + + const mockNetworkConfigurationMachine = proxyConfiguration.machine.provide(config) + const flowStates = [ + 'ACTIVATION', + 'GET_PROXY_CONFIG', + 'ADD_PROXY_CONFIGS', + 'SUCCESS' + ] + const service = createActor(mockNetworkConfigurationMachine, { input: context }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('SUCCESS') && currentStateIndex === flowStates.length) { + const status = devices[clientId].status.Network + expect(status).toEqual('Initial. Proxy Configured') + done() + } + }) + service.start() + service.send({ type: 'PROXYCONFIG', clientId }) + }) + }) + + describe('Get configs', () => { + test('should get Proxy Config', async () => { + const expectedConfig = { + proxyName: 'proxy1', + address: 'www.vprodemo.com', + infoFormat: 201, + port: 900, + networkDnsSuffix: 'intel.com' + } + const mockDb = { + proxyConfigs: { + getByName: jest.fn() + } + } + + proxyConfiguration.dbFactory = { + getDb: async () => mockDb + } as any + const getByNameSpy = jest.spyOn(mockDb.proxyConfigs, 'getByName').mockReturnValue(expectedConfig) + + await proxyConfiguration.getProxyConfig({ input: context }) + expect(context.proxyConfig).toBe(expectedConfig) + expect(getByNameSpy).toHaveBeenCalled() + }) + }) +}) diff --git a/src/stateMachines/proxyConfiguration.ts b/src/stateMachines/proxyConfiguration.ts new file mode 100644 index 000000000..45fbc1ddf --- /dev/null +++ b/src/stateMachines/proxyConfiguration.ts @@ -0,0 +1,247 @@ +/********************************************************************* + * Copyright (c) Intel Corporation 2025 + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +// import { type AMT, type CIM, type IPS } from '@device-management-toolkit/wsman-messages' +import { type IPS } from '@device-management-toolkit/wsman-messages' +import { assign, fromPromise, setup } from 'xstate' +import { type ProxyConfig } from '../models/RCS.Config.js' +import Logger from '../Logger.js' +import { type AMTConfiguration } from '../models/index.js' +import { devices } from '../devices.js' +import { Error } from './error.js' +import { Configurator } from '../Configurator.js' +import { DbCreatorFactory } from '../factories/DbCreatorFactory.js' +import { type CommonContext, invokeWsmanCall } from './common.js' +import { UNEXPECTED_PARSE_ERROR } from '../utils/constants.js' + +export interface ProxyConfigContext extends CommonContext { + amtProfile: AMTConfiguration | null + proxyConfig?: ProxyConfig + proxyConfigsCount: number + retryCount: number + proxyConfigName?: string | null + proxyConfigAdded?: string + proxyConfigFailed?: string + ips?: IPS.Messages +} + +export interface ProxyConfigEvent { + type: 'PROXYCONFIG' | 'ONFAILED' + clientId: string + output?: any +} + +export class ProxyConfiguration { + configurator: Configurator + logger: Logger + dbFactory: DbCreatorFactory + db: any + error: Error = new Error() + + getProxyConfig = async ({ input }: { input: ProxyConfigContext }): Promise => { + if (input.amtProfile?.proxyConfigs != null) { + // Get Proxy profile information based on the profile name from db. + this.db = await this.dbFactory.getDb() + input.proxyConfig = await this.db.proxyConfigs.getByName( + input.amtProfile.proxyConfigs[input.proxyConfigsCount].name, + input.amtProfile.tenantId + ) + return + } + this.logger.error('Null object in getProxyConfig()') + } + + addProxyConfigs = async ({ input }: { input: ProxyConfigContext }): Promise => { + // Add proxy config information to HTTP Proxy Service object + const proxyAccessPointParameters: IPS.Models.AddProxyAccessPointParameters = { + AccessInfo: input.proxyConfig?.address ?? '', + InfoFormat: input.proxyConfig?.infoFormat ?? 3, + Port: input.proxyConfig?.port ?? 0, + NetworkDnsSuffix: input.proxyConfig?.networkDnsSuffix ?? '' + } + + input.xmlMessage = input.ips?.HTTPProxyService.AddProxyAccessPoint(proxyAccessPointParameters) + + input.proxyConfigName = input.proxyConfig?.address ?? null + // Increment the count to keep track of proxies added to AMT + ++input.proxyConfigsCount + return await invokeWsmanCall(input) + } + + machine = setup({ + types: {} as { + context: ProxyConfigContext + events: ProxyConfigEvent + actions: any + input: ProxyConfigContext + }, + actors: { + getProxyConfig: fromPromise(this.getProxyConfig), + addProxyConfigs: fromPromise(this.addProxyConfigs), + errorMachine: this.error.machine + }, + guards: { + isMoreProxyConfigs: ({ context }) => + context.amtProfile?.proxyConfigs != null + ? context.proxyConfigsCount < context.amtProfile.proxyConfigs.length + : false, + isProxyConfigsExist: ({ context }) => + context.amtProfile?.proxyConfigs != null ? context.amtProfile.proxyConfigs.length > 0 : false, + shouldRetry: ({ context, event }) => + context.retryCount != null ? context.retryCount < 3 && event.output instanceof UNEXPECTED_PARSE_ERROR : false + }, + actions: { + 'Update Configuration Status': ({ context }) => { + const { + clientId, + proxyConfigAdded: proxyConfigsAdded, + proxyConfigFailed: proxyConfigsFailed, + statusMessage, + errorMessage + } = context + const device = devices[clientId] + const networkStatus = device.status.Network + let message + if (errorMessage) { + message = errorMessage + } else if (proxyConfigsFailed) { + message = + proxyConfigsAdded != null + ? `Added ${proxyConfigsAdded} Proxy Configurations. Failed to add ${proxyConfigsFailed}` + : `Failed to add ${proxyConfigsFailed}` + } else { + message = statusMessage + } + device.status.Network = networkStatus ? `${networkStatus}. ${message}` : message + }, + 'Reset Retry Count': assign({ retryCount: () => 0 }), + 'Increment Retry Count': assign({ retryCount: ({ context }) => context.retryCount + 1 }), + 'Check Return Value': assign({ + proxyConfigAdded: ({ context, event }) => { + if (event.output.Envelope?.Body?.AddProxyAccessPoint_OUTPUT?.ReturnValue === 0) { + if (context.proxyConfigAdded == null) { + return `${context.proxyConfigName}` + } else { + return `${context.proxyConfigAdded}, ${context.proxyConfigName}` + } + } else { + return context.proxyConfigAdded + } + }, + proxyConfigFailed: ({ context, event }) => { + if (event.output.Envelope?.Body?.AddProxyAccessPoint_OUTPUT?.ReturnValue !== 0) { + if (context.proxyConfigFailed == null) { + return `${context.proxyConfigName}` + } else { + return `${context.proxyConfigFailed}, ${context.proxyConfigName}` + } + } else { + return context.proxyConfigFailed + } + } + }) + } + }).createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAcBOB7AHgTwLQGN0A7AMwEsoBXVAQwBczjcBbG-ACzKLADoBBAMIAVAJIA1PqIDyAOQDEABQBKUgBoBNAbIBiIgOIBtAAwBdRCnSwyDYuZCZEAFgBMARh4BOIwDZnAZkcADi9nUIBWMIAaEGxEQPcwoyNXIwB2ZyNQj0cw1IBfPOi0LDxCUgpqekYiFjZObh4BAAkAUQEAaQB9bSklTuU1dU6tGV09AGU5YzMkEGRLa2q7BwQU13dA50CjD2zvR1S-V29o2NXXDx5Uoz9-Vz9vLw8-MOcCoowcAmJyKlobGqsDhcXjNNpdHp9AYaYY6fSTAyuGYWKwA5aIIynDHvOafUo-Cr-aq1YENPQtIT9FQwkZjOQQYi8LgAN3QAGteDA6Lhil8yr9pnZ5qilrMVmtAld4hcPIlUtlUicYohnKkwjxvGFvA9HF5goEDWEcbz8eU-lUmED6rxyZToUNafo5GBUBhUDxkAAbegkdCoZg8Lk8vHfM2C2bCxa2MWIVy5VI8HJHC7+baOIxRZUIZyvTzODzbHP7VyBVLHY0h-mEi2Auog-gAEQbVMGsNG8PpjJ4LPZvBoEAgwZKod+sHDKKjRHRCBc7i8vgCwUy4UzZyCCcCYWyN1CLlSavyhVxw6r5oBJOtjeb9rbY0mLrdHu9dF9-p4-cHJpHFDHpiFCzRGMZzcTwfH8IIQhXLEED8bJQO1B51jVRJvArE8CTPYkrXrMEOk6PgmxbGk4QmTolBacYFFkcYWimP8IwA0VQHFZINi2HY9gOI4lTXC4eDcXxXFVe5HBLRw0L5DDKnPbCGlwroCOvakHRI8YyIoqiZBoqYkX-EVo2Y2NWJ4TZtl2Rx9kOY5oIs9UBIuW5ZTSTUjRxIh0AgOAhUrKSiUtOtuD0ydp1wIJoNwUseGObxvFSQJROOWC-Ak01fmkrCAt4QRRAkaQZCCwDDOzbwEjjbwdlCG4XjCPxoKQzwavuJJci1DJXI+dCzXS-zSVBVo8MhIiVPbCYCqY+xEG8A0otLGq3F2NxHDq1wE1lI4-GatVfAzFLv0wnrL1tIbb30MaDIm1Z-GceCc2alaImgnN3DK9b9RyMJAl209utrXqr2Ox1RoY-SpyAlxHrLfj4luRIjG2Hd2uPSSur837L3k-DCJvQG1PIyjqJaM7QaK-ZvB4AI-EVOGvBeJasxcPwTM2DxXEcDbNnzRGv2+1GL3rbQ+BEAAZFoGyJ6cVq8fiKdLLxdUpjw6os+CAkeCyPEeGqvt8ms+YacYAFUBAECjxnFoChNuG7YbCe7VycHMrmODIpvuUIHgKAogA */ + context: ({ input }) => ({ + clientId: input.clientId, + amtProfile: input.amtProfile, + httpHandler: input.httpHandler, + message: input.message, + proxyConfigName: input.proxyConfigName, + proxyConfigsCount: input.proxyConfigsCount, + retryCount: input.retryCount, + ips: input.ips + }), + id: 'proxy-configuration-machine', + initial: 'ACTIVATION', + states: { + ACTIVATION: { + on: { + PROXYCONFIG: { + actions: [ + assign({ proxyConfigsCount: () => 0 }), + 'Reset Retry Count' + ], + target: 'CHECK_FOR_PROXY_CONFIGS' + } + } + }, + CHECK_FOR_PROXY_CONFIGS: { + always: [ + { + guard: 'isProxyConfigsExist', + target: 'GET_PROXY_CONFIG' + }, + { + target: 'SUCCESS' + } + ] + }, + GET_PROXY_CONFIG: { + invoke: { + src: 'getProxyConfig', + input: ({ context }) => context, + id: 'get-proxy-config', + onDone: { + target: 'ADD_PROXY_CONFIGS' + }, + onError: { + actions: assign({ errorMessage: 'Failed to get proxy config from DB' }), + target: 'FAILED' + } + } + }, + ADD_PROXY_CONFIGS: { + invoke: { + src: 'addProxyConfigs', + input: ({ context }) => context, + id: 'add-proxy-configs', + onDone: { + actions: 'Check Return Value', + target: 'CHECK_ADD_PROXY_CONFIGS_RESPONSE' + }, + onError: { + actions: assign({ + proxyConfigFailed: ({ context }) => + context.proxyConfigFailed == null + ? `${context.proxyConfigName}` + : `${context.proxyConfigFailed}, ${context.proxyConfigName}` + }), + target: 'CHECK_ADD_PROXY_CONFIGS_RESPONSE' + } + } + }, + CHECK_ADD_PROXY_CONFIGS_RESPONSE: { + always: [ + { + guard: 'isMoreProxyConfigs', + target: 'GET_PROXY_CONFIG' + }, + { + target: 'SUCCESS' + } + ] + }, + FAILED: { + entry: ['Update Configuration Status'], + type: 'final' + }, + SUCCESS: { + entry: [ + assign({ statusMessage: () => 'Proxy Configured' }), + 'Update Configuration Status' + ], + type: 'final' + } + } + }) + + constructor() { + this.configurator = new Configurator() + this.dbFactory = new DbCreatorFactory() + this.logger = new Logger('Network_Configuration_State_Machine') + } +} diff --git a/src/stateMachines/unconfiguration.test.ts b/src/stateMachines/unconfiguration.test.ts index d7f75b73b..174d13eaa 100644 --- a/src/stateMachines/unconfiguration.test.ts +++ b/src/stateMachines/unconfiguration.test.ts @@ -132,6 +132,16 @@ describe('Unconfiguration State Machine', () => { }) ), deleteWifiProfileOnAMTDevice: fromPromise(async ({ input }) => await Promise.resolve({ clientId })), + enumerateHTTPProxyAccessPoint: fromPromise( + async ({ input }) => await Promise.resolve({ clientId: input.clientId }) + ), + pullHTTPProxyAccessPoint: fromPromise( + async ({ input }) => + await Promise.resolve({ + Envelope: { Body: { PullResponse: { Items: { IPS_HTTPProxyAccessPoint: null } } } } + }) + ), + deleteHTTPProxyAccessPoint: fromPromise(async ({ input }) => await Promise.resolve({ clientId })), removeRemoteAccessPolicyRuleUserInitiated: fromPromise( async ({ input }) => await Promise.resolve({ clientId }) ), @@ -175,6 +185,8 @@ describe('Unconfiguration State Machine', () => { hasPublicKeyCertificate: () => false, hasEnvSettings: () => false, hasTLSCredentialContext: () => false, + hasHTTPProxyAccessPoints: () => false, + hasMoreHTTPProxyData: () => false, is8021xProfileEnabled: () => false }, delays: {} @@ -226,6 +238,177 @@ describe('Unconfiguration State Machine', () => { service.send({ type: 'REMOVECONFIG', clientId }) }) + it('should eventually reach FAILURE after ENUMERATE_HTTP_PROXY_ACCESS_POINT', (done) => { + configuration.actors!.enumerateHTTPProxyAccessPoint = fromPromise( + async ({ input }) => await Promise.reject(new Error()) + ) + const mockUnconfigurationMachine = unconfiguration.machine.provide(configuration) + const flowStates = [ + 'UNCONFIGURED', + 'ENUMERATE_ETHERNET_PORT_SETTINGS', + 'PULL_ETHERNET_PORT_SETTINGS', + 'GET_8021X_PROFILE', + 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', + 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'FAILURE' + ] + const service = createActor(mockUnconfigurationMachine, { input: unconfigContext }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('FAILURE') && currentStateIndex === flowStates.length) { + done() + } + }) + service.start() + service.send({ type: 'REMOVECONFIG', clientId }) + }) + + it('should eventually reach FAILURE after PULL_HTTP_PROXY_ACCESS_POINT', (done) => { + configuration.actors!.pullHTTPProxyAccessPoint = fromPromise(async ({ input }) => await Promise.reject(new Error())) + const mockUnconfigurationMachine = unconfiguration.machine.provide(configuration) + const flowStates = [ + 'UNCONFIGURED', + 'ENUMERATE_ETHERNET_PORT_SETTINGS', + 'PULL_ETHERNET_PORT_SETTINGS', + 'GET_8021X_PROFILE', + 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', + 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', + 'FAILURE' + ] + const service = createActor(mockUnconfigurationMachine, { input: unconfigContext }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('FAILURE') && currentStateIndex === flowStates.length) { + done() + } + }) + service.start() + service.send({ type: 'REMOVECONFIG', clientId }) + }) + + it('should eventually reach FAILURE after DELETE_HTTP_PROXY_ACCESS_POINT', (done) => { + configuration.guards!.hasHTTPProxyAccessPoints = () => true + configuration.actors!.pullHTTPProxyAccessPoint = fromPromise( + async ({ input }) => + await Promise.resolve({ + Envelope: { + Body: { + PullResponse: { + Items: { + IPS_HTTPProxyAccessPoint: [ + { Name: 'Intel(r) ME:HTTP Proxy Access Point 0' }, + { Name: 'Intel(r) ME:HTTP Proxy Access Point 1' }] + }, + EndOfSequence: '' + } + } + } + }) + ) + configuration.actors!.deleteHTTPProxyAccessPoint = fromPromise( + async ({ input }) => await Promise.reject(new Error()) + ) + const mockUnconfigurationMachine = unconfiguration.machine.provide(configuration) + const flowStates = [ + 'UNCONFIGURED', + 'ENUMERATE_ETHERNET_PORT_SETTINGS', + 'PULL_ETHERNET_PORT_SETTINGS', + 'GET_8021X_PROFILE', + 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', + 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', + 'DELETE_HTTP_PROXY_ACCESS_POINT', + 'FAILURE' + ] + const service = createActor(mockUnconfigurationMachine, { input: unconfigContext }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('FAILURE') && currentStateIndex === flowStates.length) { + done() + } + }) + service.start() + service.send({ type: 'REMOVECONFIG', clientId }) + }) + + it('should handle multiple HTTP Proxy pull operations with EnumerationContext', (done) => { + let pullCount = 0 + configuration.guards!.hasMoreHTTPProxyData = ({ context }) => { + // Check if we have EnumerationContext and no EndOfSequence + return ( + context.message.Envelope.Body.PullResponse?.EnumerationContext != null && + context.message.Envelope.Body.PullResponse?.EndOfSequence == null + ) + } + configuration.guards!.hasHTTPProxyAccessPoints = () => true + configuration.actors!.pullHTTPProxyAccessPoint = fromPromise(async ({ input }) => { + pullCount++ + if (pullCount === 1) { + // First pull response with EnumerationContext + return await Promise.resolve({ + Envelope: { + Body: { + PullResponse: { + EnumerationContext: '50270200-0000-0000-0000-000000000000', + Items: { + IPS_HTTPProxyAccessPoint: { Name: 'Intel(r) ME:HTTP Proxy Access Point 0' } + } + } + } + } + }) + } else { + // Second pull response with EndOfSequence + return await Promise.resolve({ + Envelope: { + Body: { + PullResponse: { + Items: { + IPS_HTTPProxyAccessPoint: { Name: 'Intel(r) ME:HTTP Proxy Access Point 1' } + }, + EndOfSequence: '' + } + } + } + }) + } + }) + configuration.actors!.deleteHTTPProxyAccessPoint = fromPromise( + async ({ input }) => await Promise.reject(new Error()) + ) + const mockUnconfigurationMachine = unconfiguration.machine.provide(configuration) + const flowStates = [ + 'UNCONFIGURED', + 'ENUMERATE_ETHERNET_PORT_SETTINGS', + 'PULL_ETHERNET_PORT_SETTINGS', + 'GET_8021X_PROFILE', + 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', + 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', // Second pull after EnumerationContext check + 'DELETE_HTTP_PROXY_ACCESS_POINT', + 'FAILURE' + ] + const service = createActor(mockUnconfigurationMachine, { input: unconfigContext }) + service.subscribe((state) => { + const expectedState: any = flowStates[currentStateIndex++] + expect(state.matches(expectedState)).toBe(true) + if (state.matches('FAILURE') && currentStateIndex === flowStates.length) { + done() + } + }) + service.start() + service.send({ type: 'REMOVECONFIG', clientId }) + }) + it('should eventually reach FAILURE after ENUMERATE_MANAGEMENT_PRESENCE_REMOTE_SAP', (done) => { configuration.actors!.enumerateManagementPresenceRemoteSAP = fromPromise( async ({ input }) => await Promise.reject(new Error()) @@ -238,6 +421,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -256,7 +441,7 @@ describe('Unconfiguration State Machine', () => { service.send({ type: 'REMOVECONFIG', clientId }) }) - it('should eventually reach FAILURE after REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', (done) => { + it('should eventually reach FAILURE after DELETE_WIFI_ENDPOINT_SETTINGS', (done) => { configuration.guards!.is8021xProfileEnabled = () => true configuration.actors!.pullWifiEndpointSettings = fromPromise( async ({ input }) => @@ -315,6 +500,8 @@ describe('Unconfiguration State Machine', () => { 'DISABLE_IEEE8021X_WIRED', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -344,6 +531,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -374,6 +563,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -407,6 +598,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -439,6 +632,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -462,7 +657,7 @@ describe('Unconfiguration State Machine', () => { service.send({ type: 'REMOVECONFIG', clientId }) }) - it('should eventually reach FAILURE after ENUMERATE_PUBLIC_KEY_CERTIFICATE', (done) => { + it('should eventually reach FAILURE after ENUMERATE_PUBLIC_KEY_CERTIFICATE with 8021x updated', (done) => { unconfigContext.is8021xProfileUpdated = true configuration.actors!.pullPublicPrivateKeyPair = fromPromise( async ({ input }) => await Promise.resolve({ Envelope: { Body: { PullResponse: { Items: {} } } } }) @@ -478,6 +673,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -514,6 +711,8 @@ describe('Unconfiguration State Machine', () => { 'GET_8021X_PROFILE', 'ENUMERATE_WIFI_ENDPOINT_SETTINGS', 'PULL_WIFI_ENDPOINT_SETTINGS', + 'ENUMERATE_HTTP_PROXY_ACCESS_POINT', + 'PULL_HTTP_PROXY_ACCESS_POINT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_ALERT', 'REMOVE_REMOTE_ACCESS_POLICY_RULE_PERIODIC', @@ -795,4 +994,33 @@ describe('Unconfiguration State Machine', () => { await unconfiguration.deletePublicPrivateKeyPair({ input: unconfigContext }) expect(loggerSpy).toHaveBeenCalled() }) + it('should enum HTTP Proxy Access Point', async () => { + await unconfiguration.enumerateHTTPProxyAccessPoint({ input: unconfigContext }) + expect(invokeWsmanCallSpy).toHaveBeenCalled() + }) + it('should fail enum HTTP Proxy Access Point', async () => { + unconfigContext.ips = undefined + await unconfiguration.enumerateHTTPProxyAccessPoint({ input: unconfigContext }) + expect(loggerSpy).toHaveBeenCalled() + }) + it('should pull HTTP Proxy Access Point', async () => { + unconfigContext.message = { Envelope: { Body: { EnumerateResponse: { EnumerationContext: 'xxx-xx-xxx' } } } } + await unconfiguration.pullHTTPProxyAccessPoint({ input: unconfigContext }) + expect(invokeWsmanCallSpy).toHaveBeenCalled() + }) + it('should fail pull HTTP Proxy Access Point', async () => { + unconfigContext.ips = undefined + await unconfiguration.pullHTTPProxyAccessPoint({ input: unconfigContext }) + expect(loggerSpy).toHaveBeenCalled() + }) + it('should delete HTTP Proxy Access Point', async () => { + unconfigContext.httpProxyAccessPoints = [{ Name: 'Intel(r) ME:HTTP Proxy Access Point 0' }] + await unconfiguration.deleteHTTPProxyAccessPoint({ input: unconfigContext }) + expect(invokeWsmanCallSpy).toHaveBeenCalled() + }) + it('should fail delete HTTP Proxy Access Point', async () => { + unconfigContext.ips = undefined + await unconfiguration.deleteHTTPProxyAccessPoint({ input: unconfigContext }) + expect(loggerSpy).toHaveBeenCalled() + }) }) diff --git a/src/stateMachines/unconfiguration.ts b/src/stateMachines/unconfiguration.ts index b6b6e824e..4b3727a67 100644 --- a/src/stateMachines/unconfiguration.ts +++ b/src/stateMachines/unconfiguration.ts @@ -26,6 +26,7 @@ export interface UnconfigContext extends CommonContext { privateCerts: any[] tlsSettingData: any[] publicKeyCertificates: any[] + httpProxyAccessPoints?: any[] status?: 'success' | 'error' | 'wsman' | 'heartbeat_request' ciraConfig?: CIRAConfig | null is8021xProfileUpdated?: boolean @@ -105,6 +106,42 @@ export class Unconfiguration { } } + enumerateHTTPProxyAccessPoint = async ({ input }): Promise => { + if (input.ips != null) { + input.xmlMessage = input.ips.HTTPProxyAccessPoint.Enumerate() + return await invokeWsmanCall(input, 2) + } else { + this.logger.error('Null object in enumerateHTTPProxyAccessPoint()') + } + } + + pullHTTPProxyAccessPoint = async ({ input }): Promise => { + if (input.ips != null) { + // Use EnumerationContext from PullResponse if available (for subsequent pulls), otherwise from EnumerateResponse + const enumerationContext = + input.message.Envelope.Body?.PullResponse?.EnumerationContext ?? + input.message.Envelope.Body?.EnumerateResponse?.EnumerationContext + input.xmlMessage = input.ips.HTTPProxyAccessPoint.Pull(enumerationContext) + return await invokeWsmanCall(input) + } else { + this.logger.error('Null object in pullHTTPProxyAccessPoint()') + } + } + + deleteHTTPProxyAccessPoint = async ({ input }): Promise => { + let httpProxyAccessPoints = input.httpProxyAccessPoints + if (httpProxyAccessPoints != null && input.ips != null) { + // Deletes first proxy in the array + const selector = { name: 'Name', value: httpProxyAccessPoints[0].Name } + input.xmlMessage = input.ips.HTTPProxyAccessPoint.Delete(selector) + httpProxyAccessPoints = httpProxyAccessPoints.slice(1) + input.httpProxyAccessPoints = httpProxyAccessPoints + return await invokeWsmanCall(input) + } + this.logger.error('Null object in deleteHTTPProxyAccessPoint()') + return null + } + enumerateWifiEndpointSettings = async ({ input }): Promise => { input.xmlMessage = input.cim.WiFiEndpointSettings.Enumerate() return await invokeWsmanCall(input, 2) @@ -139,6 +176,31 @@ export class Unconfiguration { } } + readHTTPProxyAccessPointPullResponse = ({ context }): void => { + let httpProxyAccessPoints: any[] = [] + if (context.message.Envelope.Body.PullResponse.Items?.IPS_HTTPProxyAccessPoint != null) { + // IPS_HTTPProxyAccessPoint is an array if there more than one profile exists, otherwise its just an object from AMT + if (Array.isArray(context.message.Envelope.Body.PullResponse.Items.IPS_HTTPProxyAccessPoint)) { + httpProxyAccessPoints = context.message.Envelope.Body.PullResponse.Items.IPS_HTTPProxyAccessPoint + } else { + httpProxyAccessPoints.push(context.message.Envelope.Body.PullResponse.Items.IPS_HTTPProxyAccessPoint) + } + } + + // Initialize array if it doesn't exist or append to existing array + if (context.httpProxyAccessPoints == null) { + context.httpProxyAccessPoints = [] + } + if (httpProxyAccessPoints.length > 0) { + // Accumulate all HTTP Proxy Access Points with Name, which is required to delete them on AMT device + httpProxyAccessPoints.forEach((proxy) => { + if (proxy.Name != null) { + context.httpProxyAccessPoints?.push({ ...proxy }) + } + }) + } + } + deleteWiFiProfileOnAMTDevice = async ({ input }): Promise => { let wifiEndpoints = input.wifiEndPointSettings if (wifiEndpoints != null && input.cim != null) { @@ -396,6 +458,9 @@ export class Unconfiguration { pullEthernetPortSettings: fromPromise(this.pullEthernetPortSettings), get8021xProfile: fromPromise(this.get8021xProfile), disableWired8021xConfiguration: fromPromise(this.disableWired8021xConfiguration), + enumerateHTTPProxyAccessPoint: fromPromise(this.enumerateHTTPProxyAccessPoint), + pullHTTPProxyAccessPoint: fromPromise(this.pullHTTPProxyAccessPoint), + deleteHTTPProxyAccessPoint: fromPromise(this.deleteHTTPProxyAccessPoint), enumerateWifiEndpointSettings: fromPromise(this.enumerateWifiEndpointSettings), pullWifiEndpointSettings: fromPromise(this.pullWifiEndpointSettings), deleteWiFiProfileOnAMTDevice: fromPromise(this.deleteWiFiProfileOnAMTDevice), @@ -443,6 +508,11 @@ export class Unconfiguration { context.message.Envelope.Body.AMT_EnvironmentDetectionSettingData.DetectionStrings != null, hasTLSCredentialContext: ({ context }) => context.message.Envelope.Body.PullResponse.Items?.AMT_TLSCredentialContext != null, + hasHTTPProxyAccessPoints: ({ context }) => + context.httpProxyAccessPoints != null ? context.httpProxyAccessPoints.length !== 0 : false, + hasMoreHTTPProxyData: ({ context }) => + context.message.Envelope.Body.PullResponse?.EnumerationContext != null && + context.message.Envelope.Body.PullResponse?.EndOfSequence == null, is8021xProfileEnabled: ({ context }) => context.message.Envelope.Body.IPS_IEEE8021xSettings.Enabled === 2 || context.message.Envelope.Body.IPS_IEEE8021xSettings.Enabled === 6, @@ -475,6 +545,7 @@ export class Unconfiguration { devices[context.clientId].unauthCount = 0 }, 'Read WiFi Endpoint Settings Pull Response': this.readWiFiEndpointSettingsPullResponse, + 'Read HTTP Proxy Access Point Pull Response': this.readHTTPProxyAccessPointPullResponse, 'Reset Retry Count': assign({ retryCount: () => 0 }), 'Increment Retry Count': assign({ retryCount: ({ context }) => context.retryCount + 1 }), 'Read Ethernet Port Settings': this.readEthernetPortSettings @@ -594,7 +665,7 @@ export class Unconfiguration { target: 'ENUMERATE_WIFI_ENDPOINT_SETTINGS' }, { - target: 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED' + target: 'ENUMERATE_HTTP_PROXY_ACCESS_POINT' } ] }, @@ -610,7 +681,7 @@ export class Unconfiguration { target: 'ENUMERATE_WIFI_ENDPOINT_SETTINGS' }, { - target: 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED' + target: 'ENUMERATE_HTTP_PROXY_ACCESS_POINT' } ], onError: { @@ -666,7 +737,7 @@ export class Unconfiguration { target: 'DELETE_WIFI_ENDPOINT_SETTINGS' }, { - target: 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED' + target: 'ENUMERATE_HTTP_PROXY_ACCESS_POINT' } ] }, @@ -695,6 +766,85 @@ export class Unconfiguration { guard: 'isWifiProfilesExistsOnDevice', target: 'DELETE_WIFI_ENDPOINT_SETTINGS' }, + { + target: 'ENUMERATE_HTTP_PROXY_ACCESS_POINT' + } + ] + }, + ENUMERATE_HTTP_PROXY_ACCESS_POINT: { + invoke: { + src: 'enumerateHTTPProxyAccessPoint', + input: ({ context }) => context, + id: 'enumerate-http-proxy-access-point', + onDone: { + actions: assign({ message: ({ event }) => event.output }), + target: 'PULL_HTTP_PROXY_ACCESS_POINT' + }, + onError: { + actions: assign({ errorMessage: () => 'Failed to enumerate HTTP Proxy Access Point' }), + target: 'FAILURE' + } + } + }, + PULL_HTTP_PROXY_ACCESS_POINT: { + invoke: { + src: 'pullHTTPProxyAccessPoint', + input: ({ context }) => context, + id: 'pull-http-proxy-access-point', + onDone: { + actions: [assign({ message: ({ event }) => event.output }), 'Reset Retry Count'], + target: 'CHECK_HTTP_PROXY_ACCESS_POINT_PULL_RESPONSE' + }, + onError: [ + { + guard: 'shouldRetry', + actions: 'Increment Retry Count', + target: 'ENUMERATE_HTTP_PROXY_ACCESS_POINT' + }, + { + actions: assign({ errorMessage: () => 'Failed to pull HTTP Proxy Access Point' }), + target: 'FAILURE' + } + ] + } + }, + CHECK_HTTP_PROXY_ACCESS_POINT_PULL_RESPONSE: { + entry: 'Read HTTP Proxy Access Point Pull Response', + always: [ + { + guard: 'hasMoreHTTPProxyData', + target: 'PULL_HTTP_PROXY_ACCESS_POINT' + }, + { + guard: 'hasHTTPProxyAccessPoints', + target: 'DELETE_HTTP_PROXY_ACCESS_POINT' + }, + { + target: 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED' + } + ] + }, + DELETE_HTTP_PROXY_ACCESS_POINT: { + invoke: { + src: 'deleteHTTPProxyAccessPoint', + input: ({ context }) => context, + id: 'delete-http-proxy-access-point', + onDone: { + actions: assign({ message: ({ event }) => event.output }), + target: 'CHECK_HTTP_PROXY_ACCESS_POINT_DELETE_RESPONSE' + }, + onError: { + actions: assign({ errorMessage: () => 'Failed to delete HTTP Proxy Access Point' }), + target: 'FAILURE' + } + } + }, + CHECK_HTTP_PROXY_ACCESS_POINT_DELETE_RESPONSE: { + always: [ + { + guard: 'hasHTTPProxyAccessPoints', + target: 'DELETE_HTTP_PROXY_ACCESS_POINT' + }, { target: 'REMOVE_REMOTE_ACCESS_POLICY_RULE_USER_INITIATED' } diff --git a/src/test/collections/rps.postman_collection.json b/src/test/collections/rps.postman_collection.json index b92a507ca..3e924d628 100644 --- a/src/test/collections/rps.postman_collection.json +++ b/src/test/collections/rps.postman_collection.json @@ -9161,7 +9161,7 @@ "});\r", "pm.test(\"Creation should succeed\", function () {\r", " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.proxyName).to.eql(\"proxyabc\");\r", + " pm.expect(jsonData.name).to.eql(\"proxyabc\");\r", " pm.expect(jsonData.address).to.eql(\"www.proxy-abc.com\");\r", " pm.expect(jsonData.infoFormat).to.eql(201);\r", " pm.expect(jsonData.port).to.eql(902);\r", @@ -9191,7 +9191,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxyabc\",\r\n \"address\": \"www.proxy-abc.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 902,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxyabc\",\r\n \"address\": \"www.proxy-abc.com\",\r\n \"port\": 902,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -9226,7 +9226,7 @@ "});\r", "pm.test(\"Request should return proxy just created\", function () {\r", " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.proxyName).to.eql(\"proxyabctest\");\r", + " pm.expect(jsonData.name).to.eql(\"proxyabctest\");\r", " pm.expect(jsonData.address).to.eql(\"www.proxy-abc.test.com\");\r", " pm.expect(jsonData.infoFormat).to.eql(201);\r", " pm.expect(jsonData.port).to.eql(800);\r", @@ -9255,7 +9255,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxyabctest\",\r\n \"address\": \"www.proxy-abc.test.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 800,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxyabctest\",\r\n \"address\": \"www.proxy-abc.test.com\",\r\n \"port\": 800,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -9316,7 +9316,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"profileName\": \"wifi-profile\",\r\n \"amtPassword\": \"Intel123!\",\r\n \"mebxPassword\": \"Intel123!\",\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"tag1\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"profileName\": \"proxy\"\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"profileName\": \"wifi-profile\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"generateRandomPassword\": false,\r\n \"generateRandomMEBxPassword\": true,\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"tag1\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"name\": \"proxy\"\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -9341,18 +9341,22 @@ "response": [] }, { - "name": "Create AMT Profile", + "name": "Create AMT Profile (proxy-profile1)", "event": [ { "listen": "test", "script": { "exec": [ + "var responseBody = pm.response.json();\r", + "versionVar = responseBody.version;\r", + "pm.globals.set(\"proxyProfile1Version\", versionVar);\r", + "\r", "pm.test(\"Status code is 201\", function () {\r", " pm.response.to.have.status(201);\r", "});\r", "pm.test(\"Creation should succeed\", function () {\r", " var result = pm.response.json();\r", - " pm.expect(result.profileName).to.eql(\"wifi-profile1\"),\r", + " pm.expect(result.profileName).to.eql(\"proxy-profile1\"),\r", " pm.expect(result.amtPassword).to.eql(),\r", " pm.expect(result.mebxPassword).to.eql(),\r", " pm.expect(result.activation).to.eql(\"ccmactivate\")\r", @@ -9360,9 +9364,9 @@ " pm.expect(result.dhcpEnabled).to.equal(true)\r", " pm.expect(result.proxyConfigs.length).to.equal(2)\r", " pm.expect(result.proxyConfigs[0].priority).to.equal(1)\r", - " pm.expect(result.proxyConfigs[0].profileName).to.equal(\"proxyabc\")\r", + " pm.expect(result.proxyConfigs[0].name).to.equal(\"proxyabc\")\r", " pm.expect(result.proxyConfigs[1].priority).to.equal(2)\r", - " pm.expect(result.proxyConfigs[1].profileName).to.equal(\"proxyabctest\")\r", + " pm.expect(result.proxyConfigs[1].name).to.equal(\"proxyabctest\")\r", "});" ], "type": "text/javascript", @@ -9387,7 +9391,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"profileName\": \"wifi-profile1\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"acm\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"profileName\": \"proxyabc\"\r\n },\r\n {\r\n \"priority\": 2,\r\n \"profileName\": \"proxyabctest\"\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"profileName\": \"proxy-profile1\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"generateRandomPassword\": false,\r\n \"generateRandomMEBxPassword\": true,\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"acm\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"name\": \"proxyabc\"\r\n },\r\n {\r\n \"priority\": 2,\r\n \"name\": \"proxyabctest\"\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -9412,7 +9416,7 @@ "response": [] }, { - "name": "Create AMT Profile", + "name": "Create AMT Profile (proxy-profile2)", "event": [ { "listen": "test", @@ -9423,7 +9427,7 @@ "});\r", "pm.test(\"Creation should succeed\", function () {\r", " var result = pm.response.json();\r", - " pm.expect(result.profileName).to.eql(\"wifi-profile2\"),\r", + " pm.expect(result.profileName).to.eql(\"proxy-profile2\"),\r", " pm.expect(result.amtPassword).to.eql(),\r", " pm.expect(result.mebxPassword).to.eql(),\r", " pm.expect(result.activation).to.eql(\"ccmactivate\")\r", @@ -9431,7 +9435,7 @@ " pm.expect(result.dhcpEnabled).to.equal(true)\r", " pm.expect(result.proxyConfigs.length).to.equal(1)\r", " pm.expect(result.proxyConfigs[0].priority).to.equal(1)\r", - " pm.expect(result.proxyConfigs[0].profileName).to.equal(\"proxyabc\")\r", + " pm.expect(result.proxyConfigs[0].name).to.equal(\"proxyabc\")\r", "});" ], "type": "text/javascript", @@ -9456,7 +9460,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"profileName\": \"wifi-profile2\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"acm\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"profileName\": \"proxyabc\"\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"profileName\": \"proxy-profile2\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"generateRandomPassword\": false,\r\n \"generateRandomMEBxPassword\": true,\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"acm\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"name\": \"proxyabc\"\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -9498,26 +9502,26 @@ "\r", "pm.test(\"Profile one\", function () {\r", " var result = pm.response.json();\r", - " pm.expect(result[0].profileName).to.eql(\"wifi-profile1\"),\r", + " pm.expect(result[0].profileName).to.eql(\"proxy-profile1\"),\r", " pm.expect(result[0].amtPassword).to.eql(),\r", " pm.expect(result[0].mebxPassword).to.eql(),\r", " pm.expect(result[0].activation).to.eql(\"ccmactivate\")\r", " pm.expect(result[0].tags.length).to.equal(1)\r", " pm.expect(result[0].proxyConfigs.length).to.equal(2)\r", " pm.expect(result[0].proxyConfigs[0].priority).to.equal(1)\r", - " pm.expect(result[0].proxyConfigs[0].profileName).to.equal(\"proxyabc\")\r", + " pm.expect(result[0].proxyConfigs[0].name).to.equal(\"proxyabc\")\r", " pm.expect(result[0].proxyConfigs[1].priority).to.equal(2)\r", - " pm.expect(result[0].proxyConfigs[1].profileName).to.equal(\"proxyabctest\")\r", + " pm.expect(result[0].proxyConfigs[1].name).to.equal(\"proxyabctest\")\r", "});\r", "\r", "pm.test(\"Profile two\", function () {\r", " var result = pm.response.json();\r", - " pm.expect(result[1].profileName).to.eql(\"wifi-profile2\"),\r", + " pm.expect(result[1].profileName).to.eql(\"proxy-profile2\"),\r", " pm.expect(result[1].activation).to.eql(\"ccmactivate\")\r", " pm.expect(result[1].tags.length).to.equal(1)\r", " pm.expect(result[1].proxyConfigs.length).to.equal(1)\r", " pm.expect(result[1].proxyConfigs[0].priority).to.equal(1)\r", - " pm.expect(result[1].proxyConfigs[0].profileName).to.equal(\"proxyabc\")\r", + " pm.expect(result[1].proxyConfigs[0].name).to.equal(\"proxyabc\")\r", "});" ], "type": "text/javascript", @@ -9543,6 +9547,312 @@ } }, "response": [] + }, + { + "name": "Create Proxy (proxy)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "pm.test(\"Request should return proxy just created\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData.name).to.eql(\"proxy\");\r", + " pm.expect(jsonData.address).to.eql(\"www.proxy.com\");\r", + " pm.expect(jsonData.infoFormat).to.eql(201);\r", + " pm.expect(jsonData.port).to.eql(8080);\r", + " pm.expect(jsonData.networkDnsSuffix).to.eql(\"intel.com\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"proxy\",\r\n \"address\": \"www.proxy.com\",\r\n \"port\": 8080,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/admin/proxyconfigs", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "admin", + "proxyconfigs" + ] + } + }, + "response": [] + }, + { + "name": "Create Profile \"profile-proxy1\" with proxyConfigs", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var responseBody = pm.response.json();\r", + "versionVar = responseBody.version;\r", + "pm.globals.set(\"profileProxy1Version\", versionVar);\r", + "\r", + "pm.test(\"Status code is 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "\r", + "pm.test(\"Profile should be created with proxyConfigs\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData.profileName).to.eql(\"profile-proxy1\");\r", + " pm.expect(jsonData.activation).to.equal(\"ccmactivate\");\r", + " pm.expect(jsonData.dhcpEnabled).to.equal(true);\r", + " pm.expect(jsonData.proxyConfigs).to.be.an('array');\r", + " pm.expect(jsonData.proxyConfigs.length).to.equal(2);\r", + " pm.expect(jsonData.proxyConfigs[0].priority).to.equal(1);\r", + " pm.expect(jsonData.proxyConfigs[0].name).to.equal(\"proxyabc\");\r", + " pm.expect(jsonData.proxyConfigs[1].priority).to.equal(2);\r", + " pm.expect(jsonData.proxyConfigs[1].name).to.equal(\"proxyabctest\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"profileName\": \"profile-proxy1\",\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"generateRandomPassword\": false,\r\n \"generateRandomMEBxPassword\": true,\r\n \"activation\": \"ccmactivate\",\r\n \"tags\": [\"proxy-test\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"name\": \"proxyabc\"\r\n },\r\n {\r\n \"priority\": 2,\r\n \"name\": \"proxyabctest\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/admin/profiles/", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "admin", + "profiles", + "" + ] + } + }, + "response": [] + }, + { + "name": "Update Profile \"profile-proxy1\" with proxyConfigs", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// Get current version of profile-proxy1\r", + "const getProfileRequest = {\r", + " url: pm.variables.get(\"protocol\") + \"://\" + pm.variables.get(\"host\") + \"/api/v1/admin/profiles/profile-proxy1\",\r", + " method: 'GET',\r", + " header: {\r", + " 'Content-Type': 'application/json'\r", + " }\r", + "};\r", + "\r", + "pm.sendRequest(getProfileRequest, function (err, response) {\r", + " if (err) {\r", + " console.log(err);\r", + " } else {\r", + " var jsonData = response.json();\r", + " pm.globals.set(\"profileProxy1Version\", jsonData.version);\r", + " }\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Should update profile with new proxyConfigs\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData.profileName).to.eql(\"profile-proxy1\");\r", + " pm.expect(jsonData.activation).to.equal(\"ccmactivate\");\r", + " pm.expect(jsonData.dhcpEnabled).to.equal(true);\r", + " pm.expect(jsonData.proxyConfigs).to.be.an('array');\r", + " pm.expect(jsonData.proxyConfigs.length).to.equal(1);\r", + " pm.expect(jsonData.proxyConfigs[0].priority).to.equal(1);\r", + " pm.expect(jsonData.proxyConfigs[0].name).to.equal(\"proxy\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"profileName\": \"profile-proxy1\",\r\n \"activation\": \"ccmactivate\",\r\n \"generateRandomPassword\": false,\r\n \"amtPassword\": \"P@ssw0rd\",\r\n \"generateRandomMEBxPassword\": true,\r\n \"tags\": [\"proxy-test\"],\r\n \"dhcpEnabled\": true,\r\n \"proxyConfigs\": [\r\n {\r\n \"priority\": 1,\r\n \"name\": \"proxy\"\r\n }\r\n ],\r\n \"version\": {{profileProxy1Version}}\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/admin/profiles/", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "admin", + "profiles", + "" + ] + } + }, + "response": [] + }, + { + "name": "Remove Profile \"profile-proxy1\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/admin/profiles/profile-proxy1", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "admin", + "profiles", + "profile-proxy1" + ] + } + }, + "response": [] + }, + { + "name": "Remove Proxy (proxy)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{protocol}}://{{host}}/api/v1/admin/proxyconfigs/proxy", + "protocol": "{{protocol}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v1", + "admin", + "proxyconfigs", + "proxy" + ] + } + }, + "response": [] } ] }, @@ -12719,7 +13029,7 @@ "pm.test(\"Result should contain an error and message\", function () {\r", " var jsonData = pm.response.json();\r", " pm.expect(jsonData.errors[0].msg).to.eql(\"Proxy profile name is required\");\r", - " pm.expect(jsonData.errors[0].path).to.eql(\"proxyName\");\r", + " pm.expect(jsonData.errors[0].path).to.eql(\"name\");\r", "});" ], "type": "text/javascript", @@ -12743,7 +13053,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"\",\r\n \"address\": \"proxy.dummy.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"\",\r\n \"address\": \"proxy.dummy.com\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -12803,7 +13113,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxy1\",\r\n \"address\": \"\",\r\n \"infoFormat\": 3,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxy1\",\r\n \"address\": \"\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -12828,7 +13138,7 @@ "response": [] }, { - "name": "Create Proxy with invalid info format", + "name": "Create Proxy with invalid address format", "event": [ { "listen": "test", @@ -12839,8 +13149,8 @@ "});\r", "pm.test(\"Result should contain an error and message\", function () {\r", " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.errors[0].msg).to.eql(\"Server address format should be either 3(IPV4), 4(IPV6) or 201(FQDN)\");\r", - " pm.expect(jsonData.errors[0].path).to.eql(\"infoFormat\");\r", + " pm.expect(jsonData.errors[0].msg).to.eql(\"Server address must be a valid IPv4, IPv6, or FQDN\");\r", + " pm.expect(jsonData.errors[0].path).to.eql(\"address\");\r", "});" ], "type": "text/javascript", @@ -12863,7 +13173,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxy1\",\r\n \"address\": \"192.168.9.58\",\r\n \"infoFormat\": 5,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxy1\",\r\n \"address\": \"invalid@address!format\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -12923,7 +13233,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxy1\",\r\n \"address\": \"192.168.9.58\",\r\n \"infoFormat\": 3,\r\n \"port\": 65536,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxy1\",\r\n \"address\": \"192.168.9.58\",\r\n \"port\": 65536,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -12983,7 +13293,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"proxy1\",\r\n \"address\": \"192.168.9.58\",\r\n \"infoFormat\": 3,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"-intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"proxy1\",\r\n \"address\": \"192.168.9.58\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"-intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -13046,7 +13356,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"{{proxyName}}\",\r\n \"address\": \"www.vprodemo.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"{{proxyName}}\",\r\n \"address\": \"www.vprodemo.com\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -13107,7 +13417,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"{{proxyName}}\",\r\n \"address\": \"www.vprodemo.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"{{proxyName}}\",\r\n \"address\": \"www.vprodemo.com\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -13143,7 +13453,7 @@ "});\r", "pm.test(\"Request should return proxy just modified\", function () {\r", " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.proxyName).to.eql(\"proxy1\");\r", + " pm.expect(jsonData.name).to.eql(\"proxy1\");\r", " pm.expect(jsonData.address).to.eql(\"www.vprotest.com\");\r", " pm.expect(jsonData.infoFormat).to.eql(201);\r", " pm.expect(jsonData.port).to.eql(900);\r", @@ -13171,7 +13481,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"{{proxyName}}\",\r\n \"address\": \"www.vprotest.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"{{proxyName}}\",\r\n \"address\": \"www.vprotest.com\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json" @@ -13232,7 +13542,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"proxyName\": \"nonExistingProxy\",\r\n \"address\": \"www.vprotest.com\",\r\n \"infoFormat\": 201,\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", + "raw": "{\r\n \"name\": \"nonExistingProxy\",\r\n \"address\": \"www.vprotest.com\",\r\n \"port\": 900,\r\n \"networkDnsSuffix\": \"intel.com\"\r\n}", "options": { "raw": { "language": "json"