From db2a6ef23f0294a1c23ef8b3388fab5456197e3c Mon Sep 17 00:00:00 2001 From: Sterfive's NodeWoT team Date: Tue, 2 Sep 2025 14:25:30 +0200 Subject: [PATCH 1/4] feat(core): add support for combo security ComboSecurityScheme #1416 --- packages/core/src/consumed-thing.ts | 9 +++- packages/core/test/ClientTest.ts | 68 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 67d10482b..de55d2c36 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -41,6 +41,7 @@ import UriTemplate = require("uritemplate"); import { InteractionOutput, ActionInteractionOutput } from "./interaction-output"; import { ActionElement, + ComboSecurityScheme, EventElement, FormElementEvent, FormElementProperty, @@ -449,7 +450,13 @@ export default class ConsumedThing extends Thing implements IConsumedThing { const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) // also push nosec in case of proxy if (ws != null) { - scs.push(ws); + if (ws.scheme === "combo") { + const combo = ws as ComboSecurityScheme; + const schemes = this.getSecuritySchemes(combo.allOf as string[]); + scs.push(...schemes); + } else { + scs.push(ws); + } } } return scs; diff --git a/packages/core/test/ClientTest.ts b/packages/core/test/ClientTest.ts index 47c6819ad..9ffc78377 100644 --- a/packages/core/test/ClientTest.ts +++ b/packages/core/test/ClientTest.ts @@ -805,4 +805,72 @@ class WoTClientTest { // eslint-disable-next-line no-unused-expressions expect(WoTClientTest.servient.hasClientFor(tcf2.scheme)).to.be.not.true; } + + @test "ensure combo security "(done: Mocha.Done) { + try { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + basic_sc: { + scheme: "basic", + }, + opcua_secure_channel_sc: { + scheme: "opcua-channel-security", + }, + opcua_authetication_sc: { + scheme: "opcua-authentication", + }, + combo_sc: { + scheme: "combo", + allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], + }, + }; + ct.security = ["combo_sc"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + // security: ["apikey_sc"], + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(2); + expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); + expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + done(); + } catch (err) { + done(err); + } + } + + @test "ensure combo security in form"(done: Mocha.Done) { + try { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + basic_sc: { + scheme: "basic", + }, + opcua_secure_channel_sc: { + scheme: "opcua-channel-security", + }, + opcua_authetication_sc: { + scheme: "opcua-authentication", + }, + combo_sc: { + scheme: "combo", + allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], + }, + }; + ct.security = "basic"; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + security: ["combo_sc"], + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(2); + expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); + expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + done(); + } catch (err) { + done(err); + } + } } From ab428521be46f4c9058eacdd33d63242109c428f Mon Sep 17 00:00:00 2001 From: Sterfive's NodeWoT team Date: Tue, 2 Sep 2025 14:49:37 +0200 Subject: [PATCH 2/4] feat(core): ensure combo resolution doesn't go into infinite loop #1416 --- packages/core/src/consumed-thing.ts | 31 +++--- packages/core/test/ClientTest.ts | 140 +++++++++++++++------------- 2 files changed, 94 insertions(+), 77 deletions(-) diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index de55d2c36..7ccb54129 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -445,20 +445,29 @@ export default class ConsumedThing extends Thing implements IConsumedThing { } getSecuritySchemes(security: Array): Array { + const visited = new Set(); + const scs: Array = []; - for (const s of security) { - const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) - // also push nosec in case of proxy - if (ws != null) { - if (ws.scheme === "combo") { - const combo = ws as ComboSecurityScheme; - const schemes = this.getSecuritySchemes(combo.allOf as string[]); - scs.push(...schemes); - } else { - scs.push(ws); + const visitSchemes = (security: Array) => { + for (const s of security) { + if (visited.has(s)) { + continue; + } + visited.add(s); + + const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) + // also push nosec in case of proxy + if (ws != null) { + if (ws.scheme === "combo") { + const combo = ws as ComboSecurityScheme; + visitSchemes(combo.allOf as string[]); + } else { + scs.push(ws); + } } } - } + }; + visitSchemes(security); return scs; } diff --git a/packages/core/test/ClientTest.ts b/packages/core/test/ClientTest.ts index 9ffc78377..386d5969e 100644 --- a/packages/core/test/ClientTest.ts +++ b/packages/core/test/ClientTest.ts @@ -806,71 +806,79 @@ class WoTClientTest { expect(WoTClientTest.servient.hasClientFor(tcf2.scheme)).to.be.not.true; } - @test "ensure combo security "(done: Mocha.Done) { - try { - const ct = new ConsumedThing(WoTClientTest.servient); - ct.securityDefinitions = { - basic_sc: { - scheme: "basic", - }, - opcua_secure_channel_sc: { - scheme: "opcua-channel-security", - }, - opcua_authetication_sc: { - scheme: "opcua-authentication", - }, - combo_sc: { - scheme: "combo", - allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], - }, - }; - ct.security = ["combo_sc"]; - const pc = new TestProtocolClient(); - const form: Form = { - href: "https://example.com/", - // security: ["apikey_sc"], - }; - ct.ensureClientSecurity(pc, form); - expect(pc.securitySchemes.length).equals(2); - expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); - expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); - done(); - } catch (err) { - done(err); - } - } - - @test "ensure combo security in form"(done: Mocha.Done) { - try { - const ct = new ConsumedThing(WoTClientTest.servient); - ct.securityDefinitions = { - basic_sc: { - scheme: "basic", - }, - opcua_secure_channel_sc: { - scheme: "opcua-channel-security", - }, - opcua_authetication_sc: { - scheme: "opcua-authentication", - }, - combo_sc: { - scheme: "combo", - allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], - }, - }; - ct.security = "basic"; - const pc = new TestProtocolClient(); - const form: Form = { - href: "https://example.com/", - security: ["combo_sc"], - }; - ct.ensureClientSecurity(pc, form); - expect(pc.securitySchemes.length).equals(2); - expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); - expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); - done(); - } catch (err) { - done(err); - } + @test "ensure combo security"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + basic_sc: { + scheme: "basic", + }, + opcua_secure_channel_sc: { + scheme: "opcua-channel-security", + }, + opcua_authetication_sc: { + scheme: "opcua-authentication", + }, + combo_sc: { + scheme: "combo", + allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], + }, + }; + ct.security = ["combo_sc"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(2); + expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); + expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + } + + @test "ensure combo security in form"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + basic_sc: { + scheme: "basic", + }, + opcua_secure_channel_sc: { + scheme: "opcua-channel-security", + }, + opcua_authetication_sc: { + scheme: "opcua-authentication", + }, + combo_sc: { + scheme: "combo", + allOf: ["opcua_secure_channel_sc", "opcua_authetication_sc"], + }, + }; + ct.security = "basic"; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + security: ["combo_sc"], + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(2); + expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); + expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + } + + @test "ensure no infinite loop with recursive combo security"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + // a badly designed combo that goes into infinite loop + combo_sc: { + scheme: "combo", + allOf: ["combo_sc", "combo_sc"], + }, + }; + ct.security = "basic"; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + security: ["combo_sc"], + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(0); } } From 0bc5c88e27a0ea48490764536f3f27a81670ee97 Mon Sep 17 00:00:00 2001 From: Sterfive's NodeWoT team Date: Thu, 4 Sep 2025 14:35:56 +0200 Subject: [PATCH 3/4] feat(core): refine combo security scheme implementation #1416 This commit refines the implementation of combo security schemes by introducing explicit AllOfSecurityScheme and OneOfSecurityScheme types. The getSecuritySchemes method in ConsumedThing has been updated to recursively resolve these schemes, and the tests have been expanded to cover nested allOf and oneOf scenarios. --- packages/core/src/consumed-thing.ts | 48 ++++++--- packages/core/src/thing-description.ts | 16 ++- packages/core/test/ClientTest.ts | 139 +++++++++++++++++++++++-- 3 files changed, 178 insertions(+), 25 deletions(-) diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 7ccb54129..e7f1257e0 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -24,6 +24,8 @@ import { ThingAction, ThingEvent, SecurityScheme, + AllOfSecurityScheme, + OneOfSecurityScheme, } from "./thing-description"; import { ThingModel } from "wot-thing-model-types"; @@ -445,30 +447,50 @@ export default class ConsumedThing extends Thing implements IConsumedThing { } getSecuritySchemes(security: Array): Array { - const visited = new Set(); + const alreadyProcessed = new Map(); - const scs: Array = []; const visitSchemes = (security: Array) => { + const resolveComboScheme = ( + combo: ComboSecurityScheme + ): AllOfSecurityScheme | OneOfSecurityScheme | undefined => { + if (combo.allOf instanceof Array) { + const allOf = visitSchemes(combo.allOf as string[]); + return { + scheme: "combo", + allOf, + }; + } + if (combo.oneOf instanceof Array) { + const oneOf = visitSchemes(combo.oneOf as string[]); + return { + scheme: "combo", + oneOf, + }; + } + return undefined; // not supported , but handled gracefully + }; + const scs: SecurityScheme[] = []; for (const s of security) { - if (visited.has(s)) { + if (alreadyProcessed.has(s)) { + scs.push(alreadyProcessed.get(s)!); continue; } - visited.add(s); + alreadyProcessed.set(s, null); - const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) + let ws: SecurityScheme | undefined = this.securityDefinitions[s]; // also push nosec in case of proxy + if (ws?.scheme === "combo") { + ws = resolveComboScheme(ws as ComboSecurityScheme); + } if (ws != null) { - if (ws.scheme === "combo") { - const combo = ws as ComboSecurityScheme; - visitSchemes(combo.allOf as string[]); - } else { - scs.push(ws); - } + scs.push(ws); + // remember in case we came accross the same again + alreadyProcessed.set(s, ws); } } + return scs; }; - visitSchemes(security); - return scs; + return visitSchemes(security); } ensureClientSecurity(client: ProtocolClient, form: Form | undefined): void { diff --git a/packages/core/src/thing-description.ts b/packages/core/src/thing-description.ts index 61fc2433e..4b69f6471 100644 --- a/packages/core/src/thing-description.ts +++ b/packages/core/src/thing-description.ts @@ -135,7 +135,6 @@ export interface NullSchema extends BaseSchema { } // TODO AutoSecurityScheme -// TODO ComboSecurityScheme export type SecurityType = | NoSecurityScheme | BasicSecurityScheme @@ -143,7 +142,9 @@ export type SecurityType = | BearerSecurityScheme | APIKeySecurityScheme | OAuth2SecurityScheme - | PSKSecurityScheme; + | PSKSecurityScheme + | AllOfSecurityScheme + | OneOfSecurityScheme; export interface SecurityScheme { scheme: string; @@ -180,6 +181,17 @@ export interface PSKSecurityScheme extends SecurityScheme, TDT.PskSecurityScheme export interface OAuth2SecurityScheme extends SecurityScheme, TDT.OAuth2SecurityScheme { scheme: "oauth2"; } +export interface OneOfSecurityScheme extends SecurityScheme { + scheme: "combo"; + oneOf: SecurityScheme[]; + allOf: never; +} +export interface AllOfSecurityScheme extends SecurityScheme { + scheme: "combo"; + allOf: SecurityScheme[]; + oneOf: never; +} +export type ComboSecurityScheme = AllOfSecurityScheme | OneOfSecurityScheme; /** Implements the Thing Property description */ export abstract class ThingProperty extends BaseSchema { diff --git a/packages/core/test/ClientTest.ts b/packages/core/test/ClientTest.ts index 386d5969e..6debd8175 100644 --- a/packages/core/test/ClientTest.ts +++ b/packages/core/test/ClientTest.ts @@ -27,7 +27,7 @@ import { Subscription } from "rxjs/Subscription"; import Servient from "../src/servient"; import ConsumedThing from "../src/consumed-thing"; -import { Form, SecurityScheme } from "../src/thing-description"; +import { AllOfSecurityScheme, Form, OneOfSecurityScheme, SecurityScheme } from "../src/thing-description"; import { ProtocolClient, ProtocolClientFactory } from "../src/protocol-interfaces"; import { Content } from "../src/content"; import { ContentSerdes } from "../src/content-serdes"; @@ -806,7 +806,7 @@ class WoTClientTest { expect(WoTClientTest.servient.hasClientFor(tcf2.scheme)).to.be.not.true; } - @test "ensure combo security"() { + @test "ensure combo security - allOf"() { const ct = new ConsumedThing(WoTClientTest.servient); ct.securityDefinitions = { basic_sc: { @@ -829,12 +829,69 @@ class WoTClientTest { href: "https://example.com/", }; ct.ensureClientSecurity(pc, form); - expect(pc.securitySchemes.length).equals(2); - expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); - expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + expect(pc.securitySchemes.length).equals(1); + expect(pc.securitySchemes[0].scheme).equals("combo"); + + const comboScheme = pc.securitySchemes[0] as AllOfSecurityScheme; + expect(comboScheme.allOf).instanceOf(Array); + expect(comboScheme.allOf.length).equal(2); + expect(comboScheme.allOf[0].scheme).equals("opcua-channel-security"); + expect(comboScheme.allOf[1].scheme).equals("opcua-authentication"); + } + + @test "ensure combo security - oneOf"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + basic_sc: { + scheme: "basic", + }, + opcua_secure_channel_encrypt_sc: { + scheme: "opcua-channel-security", + mode: "encrypt", + }, + opcua_secure_channel_sign_sc: { + scheme: "opcua-channel-security", + mode: "sign", + }, + opcua_authetication_sc: { + scheme: "opcua-authentication", + }, + comob_opcua_secure_channel: { + scheme: "combo", + oneOf: ["opcua_secure_channel_encrypt_sc", "opcua_secure_channel_sign_sc"], + }, + combo_sc: { + scheme: "combo", + allOf: ["comob_opcua_secure_channel", "opcua_authetication_sc"], + }, + }; + ct.security = ["combo_sc"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(1); + expect(pc.securitySchemes[0].scheme).equals("combo"); + + const comboScheme = pc.securitySchemes[0] as AllOfSecurityScheme; + + expect(comboScheme.allOf).instanceOf(Array); + expect(comboScheme.allOf.length).equal(2); + expect(comboScheme.allOf[0].scheme).equals("combo"); + expect(comboScheme.allOf[1].scheme).equals("opcua-authentication"); + + // + const firstScheme = comboScheme.allOf[0] as OneOfSecurityScheme; + expect(firstScheme.scheme).equal("combo"); + expect(firstScheme.oneOf).instanceOf(Array); + + expect(firstScheme.oneOf.length).equal(2); + expect(firstScheme.oneOf[0].scheme).equal("opcua-channel-security"); + expect(firstScheme.oneOf[0].scheme).equal("opcua-channel-security"); } - @test "ensure combo security in form"() { + @test "ensure combo security in form - allOf"() { const ct = new ConsumedThing(WoTClientTest.servient); ct.securityDefinitions = { basic_sc: { @@ -858,9 +915,11 @@ class WoTClientTest { security: ["combo_sc"], }; ct.ensureClientSecurity(pc, form); - expect(pc.securitySchemes.length).equals(2); - expect(pc.securitySchemes[0].scheme).equals("opcua-channel-security"); - expect(pc.securitySchemes[1].scheme).equals("opcua-authentication"); + expect(pc.securitySchemes.length).equals(1); + const comboScheme = pc.securitySchemes[0] as AllOfSecurityScheme; + + expect(comboScheme.allOf[0].scheme).equals("opcua-channel-security"); + expect(comboScheme.allOf[1].scheme).equals("opcua-authentication"); } @test "ensure no infinite loop with recursive combo security"() { @@ -879,6 +938,66 @@ class WoTClientTest { security: ["combo_sc"], }; ct.ensureClientSecurity(pc, form); - expect(pc.securitySchemes.length).equals(0); + expect(pc.securitySchemes.length).equals(1); + } + + @test "complex combo security with repeated elements"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + // a badly designed combo that goes into infinite loop + a: { + scheme: "a", + }, + b: { + scheme: "b", + }, + c: { + scheme: "c", + }, + combo_a_and_b: { + scheme: "combo", + allOf: ["a", "b"], + }, + combo_a_and_c: { + scheme: "combo", + allOf: ["a", "c"], + }, + combo_a_or_b: { + scheme: "combo", + oneOf: ["a", "b"], + }, + combo_of_combo: { + scheme: "combo", + oneOf: ["combo_a_and_b", "combo_a_and_c"], + }, + }; + ct.security = ["combo_of_combo"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + }; + ct.ensureClientSecurity(pc, form); + expect(pc.securitySchemes.length).equals(1); + expect(pc.securitySchemes[0].scheme).equal("combo"); + const comboOfCombo = pc.securitySchemes[0] as OneOfSecurityScheme; + expect(comboOfCombo.oneOf).instanceOf(Array); + expect(comboOfCombo.oneOf.length).equal(2); + expect(comboOfCombo.oneOf[0].scheme).equal("combo"); + expect(comboOfCombo.oneOf[1].scheme).equal("combo"); + + const first = comboOfCombo.oneOf[0] as AllOfSecurityScheme; + expect(first.allOf).instanceOf(Array); + expect(first.allOf[0].scheme).equal("a"); + expect(first.allOf[1].scheme).equal("b"); + + const second = comboOfCombo.oneOf[1] as AllOfSecurityScheme; + expect(second.allOf).instanceOf(Array); + expect(second.allOf[0].scheme).equal("a"); + expect(second.allOf[1].scheme).equal("c"); + + // Verfy that a has been processed once - with strict equality + const a1 = first.allOf[0]; + const a2 = second.allOf[0]; + expect(a1).equals(a2); } } From c9fa3db575fc3ffbec000622ac4f182ab358fbe8 Mon Sep 17 00:00:00 2001 From: Sterfive's NodeWoT team Date: Tue, 9 Sep 2025 09:47:59 +0200 Subject: [PATCH 4/4] feat(core): add validation for combo security schemes #1416 This commit introduces validation for combo security schemes to ensure that a combo scheme contains either 'allOf' or 'oneOf', but not both. - Throws an error if a combo scheme is invalid. - Adds unit tests to verify the new validation logic. --- packages/core/src/consumed-thing.ts | 14 +++++---- packages/core/test/ClientTest.ts | 47 ++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index e7f1257e0..ee095efda 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -451,23 +451,25 @@ export default class ConsumedThing extends Thing implements IConsumedThing { const visitSchemes = (security: Array) => { const resolveComboScheme = ( - combo: ComboSecurityScheme + combo: ComboSecurityScheme, + name: string ): AllOfSecurityScheme | OneOfSecurityScheme | undefined => { - if (combo.allOf instanceof Array) { + if (combo.allOf instanceof Array && combo.oneOf === undefined) { const allOf = visitSchemes(combo.allOf as string[]); return { scheme: "combo", allOf, }; - } - if (combo.oneOf instanceof Array) { + } else if (combo.oneOf instanceof Array && combo.allOf === undefined) { const oneOf = visitSchemes(combo.oneOf as string[]); return { scheme: "combo", oneOf, }; + } else { + // invalid combination that should be spotted by the TD schema verificator + throw new Error(`Combo SecurityScheme '${name}' is invalid`); } - return undefined; // not supported , but handled gracefully }; const scs: SecurityScheme[] = []; for (const s of security) { @@ -480,7 +482,7 @@ export default class ConsumedThing extends Thing implements IConsumedThing { let ws: SecurityScheme | undefined = this.securityDefinitions[s]; // also push nosec in case of proxy if (ws?.scheme === "combo") { - ws = resolveComboScheme(ws as ComboSecurityScheme); + ws = resolveComboScheme(ws as ComboSecurityScheme, s); } if (ws != null) { scs.push(ws); diff --git a/packages/core/test/ClientTest.ts b/packages/core/test/ClientTest.ts index 6debd8175..0ce53283d 100644 --- a/packages/core/test/ClientTest.ts +++ b/packages/core/test/ClientTest.ts @@ -21,7 +21,7 @@ */ import { suite, test } from "@testdeck/mocha"; -import { expect, should, use as chaiUse } from "chai"; +import { expect, should, use as chaiUse, assert } from "chai"; import { Subscription } from "rxjs/Subscription"; @@ -1000,4 +1000,49 @@ class WoTClientTest { const a2 = second.allOf[0]; expect(a1).equals(a2); } + + @test "invalid combo with allOf AND onOf should be detected and throw"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + // a badly designed combo has allOf and oneOf + a: { + scheme: "a", + }, + b: { + scheme: "b", + }, + combo_oneOf_and_allof: { + scheme: "combo", + allOf: ["a", "b"], + oneOf: ["a", "b"], + }, + }; + ct.security = ["combo_oneOf_and_allof"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + }; + assert.throws(() => { + ct.ensureClientSecurity(pc, form); + }, /Combo SecurityScheme 'combo_oneOf_and_allof' is invalid/); + } + + @test "invalid combo with missing allOf and oneOf should be detected and throw"() { + const ct = new ConsumedThing(WoTClientTest.servient); + ct.securityDefinitions = { + // a badly designed combo has NO allOf and NO oneOf + + combo_without_oneOf_and_without_allof: { + scheme: "combo", + }, + }; + ct.security = ["combo_without_oneOf_and_without_allof"]; + const pc = new TestProtocolClient(); + const form: Form = { + href: "https://example.com/", + }; + assert.throws(() => { + ct.ensureClientSecurity(pc, form); + }, /Combo SecurityScheme 'combo_without_oneOf_and_without_allof' is invalid/); + } }