From c4c2a026185fde31bd399eab97b2504d14ff449c Mon Sep 17 00:00:00 2001 From: Monika Singh Date: Tue, 29 Aug 2023 15:33:50 +0200 Subject: [PATCH] Addition of the security parameter in Asyncapi --- .../td_to_asyncapi/examples/asyncapi.json | 274 ++++---- packages/td_to_asyncapi/examples/td.json | 302 ++++---- packages/td_to_asyncapi/index.js | 66 +- packages/td_to_asyncapi/src/mapSecurity.js | 645 ++++++++++-------- 4 files changed, 665 insertions(+), 622 deletions(-) diff --git a/packages/td_to_asyncapi/examples/asyncapi.json b/packages/td_to_asyncapi/examples/asyncapi.json index 92ac5a099..8d6887d81 100644 --- a/packages/td_to_asyncapi/examples/asyncapi.json +++ b/packages/td_to_asyncapi/examples/asyncapi.json @@ -1,148 +1,148 @@ { - "asyncapi": "2.0.0", - "info": { - "title": "MyCoffeeMaker-Home", - "version": "unknown", - "description": "Order your coffee remotely!", - "x-AT-context": [ - "https://www.w3.org/2022/wot/td/v1.1", - { - "cov": "http://www.example.org/coap-binding#", - "mqv": "http://www.example.org/mqtt-binding#" - }, - { - "@language": "en" - } + "asyncapi": "2.0.0", + "info": { + "title": "MyCoffeeMaker-Home", + "version": "unknown", + "description": "Order your coffee remotely!", + "x-AT-context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "cov": "http://www.example.org/coap-binding#", + "mqv": "http://www.example.org/mqtt-binding#" + }, + { + "@language": "en" + } + ], + "x-AT-type": "Thing", + "x-name": "MyCoffeeMaker" + }, + "channels": { + "/oneOfTest": { + "subscribe": { + "operationId": "observeoneOfTestproperty", + "tags": [ + { + "name": "properties" + } ], - "x-AT-type": "Thing", - "x-name": "MyCoffeeMaker" - }, - "channels": { - "/oneOfTest": { - "subscribe": { - "operationId": "observeoneOfTestproperty", - "tags": [ - { - "name": "properties" - } - ], - "message": { - "contentType": "application/json", - "payload": { - "oneOf": [ - { - "type": "string", - "readOnly": false, - "writeOnly": false - }, - { - "type": "integer", - "readOnly": false, - "writeOnly": false - }, - { - "type": "null", - "readOnly": false, - "writeOnly": false - } - ], - "readOnly": true, - "writeOnly": false - } - }, - "bindings": { - "mqtt": { - "bindingVersion": "0.1.0", - "qos": 1 - } - } - } + "message": { + "contentType": "application/json", + "payload": { + "oneOf": [ + { + "type": "string", + "readOnly": false, + "writeOnly": false + }, + { + "type": "integer", + "readOnly": false, + "writeOnly": false + }, + { + "type": "null", + "readOnly": false, + "writeOnly": false + } + ], + "readOnly": true, + "writeOnly": false + } }, - "/maxItemsTest": { - "subscribe": { - "operationId": "observemaxItemsTestproperty", - "tags": [ - { - "name": "properties" - } - ], - "message": { - "contentType": "application/json", - "payload": { - "type": "array", - "readOnly": true, - "writeOnly": false - } - } - } - }, - "/binfull": { - "subscribe": { - "operationId": "subscribebinFullevent", - "tags": [ - { - "name": "events" - } - ], - "message": { - "contentType": "application/json", - "payload": { - "type": "number", - "readOnly": false, - "writeOnly": false - } - } - } + "bindings": { + "mqtt": { + "bindingVersion": "0.1.0", + "qos": 1 + } } + } }, - "id": "urn:dev:home:coff:type123-SNR123456", - "servers": { - "0": { - "url": "mqtt://iot.eclipse.org", - "protocol": "mqtt" - }, - "1": { - "url": "mqtt://mycoffeemaker.example.com", - "protocol": "mqtt" + "/maxItemsTest": { + "subscribe": { + "operationId": "observemaxItemsTestproperty", + "tags": [ + { + "name": "properties" + } + ], + "message": { + "contentType": "application/json", + "payload": { + "type": "array", + "readOnly": true, + "writeOnly": false + } } + } }, - "tags": [ - { - "name": "properties", - "description": "A property can expose a variable of a Thing, this variable might be readable, writable and/or observable.", - "externalDocs": { - "url": "https://www.w3.org/TR/wot-thing-description/#propertyaffordance", - "description": "Find out more about Property Affordances." - } - }, - { - "name": "actions", - "description": "An action can expose something to be executed by a Thing, an action can be invoked.", - "externalDocs": { - "url": "https://www.w3.org/TR/wot-thing-description/#actionaffordance", - "description": "Find out more about Action Affordances." - } - }, - { - "name": "events", - "description": "An event can expose a notification by a Thing, this notification can be subscribed and/or unsubscribed.", - "externalDocs": { - "url": "https://www.w3.org/TR/wot-thing-description/#eventaffordance", - "description": "Find out more about Event Affordances." - } - } - ], - "components": { - "securitySchemes": { - "basic_sc": { - "type": "http", - "scheme": "basic", - "x-in": "header" - } + "/binfull": { + "subscribe": { + "operationId": "subscribebinFullevent", + "tags": [ + { + "name": "events" + } + ], + "message": { + "contentType": "application/json", + "payload": { + "type": "number", + "readOnly": false, + "writeOnly": false + } } + } + } + }, + "id": "urn:dev:home:coff:type123-SNR123456", + "servers": { + "0": { + "url": "mqtt://iot.eclipse.org", + "protocol": "mqtt" }, - "externalDocs": { - "url": "http://plugfest.thingweb.io/playground/", - "description": "This AsyncAPI instance was generated from a Web of Things (WoT) - Thing Description by the WoT Playground" + "1": { + "url": "mqtt://mycoffeemaker.example.com", + "protocol": "mqtt" + } + }, + "tags": [ + { + "name": "properties", + "description": "A property can expose a variable of a Thing, this variable might be readable, writable and/or observable.", + "externalDocs": { + "url": "https://www.w3.org/TR/wot-thing-description/#propertyaffordance", + "description": "Find out more about Property Affordances." + } + }, + { + "name": "actions", + "description": "An action can expose something to be executed by a Thing, an action can be invoked.", + "externalDocs": { + "url": "https://www.w3.org/TR/wot-thing-description/#actionaffordance", + "description": "Find out more about Action Affordances." + } + }, + { + "name": "events", + "description": "An event can expose a notification by a Thing, this notification can be subscribed and/or unsubscribed.", + "externalDocs": { + "url": "https://www.w3.org/TR/wot-thing-description/#eventaffordance", + "description": "Find out more about Event Affordances." + } + } + ], + "components": { + "securitySchemes": { + "basic_sc": { + "type": "http", + "scheme": "basic", + "x-in": "header" + } } + }, + "externalDocs": { + "url": "http://plugfest.thingweb.io/playground/", + "description": "This AsyncAPI instance was generated from a Web of Things (WoT) - Thing Description by the WoT Playground" + } } diff --git a/packages/td_to_asyncapi/examples/td.json b/packages/td_to_asyncapi/examples/td.json index 8c6a0d72e..e18358f30 100644 --- a/packages/td_to_asyncapi/examples/td.json +++ b/packages/td_to_asyncapi/examples/td.json @@ -1,163 +1,163 @@ { - "@context": [ - "https://www.w3.org/2022/wot/td/v1.1", + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { + "cov": "http://www.example.org/coap-binding#", + "mqv": "http://www.example.org/mqtt-binding#" + }, + { "@language": "en" } + ], + "id": "urn:dev:home:coff:type123-SNR123456", + "name": "MyCoffeeMaker", + "@type": "Thing", + "title": "MyCoffeeMaker-Home", + "description": "Order your coffee remotely!", + "securityDefinitions": { + "basic_sc": { "scheme": "basic", "in": "header" }, + "psk_sc": { "scheme": "psk" }, + "nosec_sc": { "scheme": "nosec" } + }, + "security": ["nosec_sc"], + "properties": { + "oneOfTest": { + "readOnly": true, + "writeOnly": false, + "oneOf": [ { - "cov": "http://www.example.org/coap-binding#", - "mqv": "http://www.example.org/mqtt-binding#" + "type": "string" }, - { "@language": "en" } - ], - "id": "urn:dev:home:coff:type123-SNR123456", - "name": "MyCoffeeMaker", - "@type": "Thing", - "title": "MyCoffeeMaker-Home", - "description": "Order your coffee remotely!", - "securityDefinitions": { - "basic_sc": { "scheme": "basic", "in": "header" }, - "psk_sc": { "scheme": "psk" }, - "nosec_sc": { "scheme": "nosec" } - }, - "security": ["nosec_sc"], - "properties": { - "oneOfTest": { - "readOnly": true, - "writeOnly": false, - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "null" - } - ], - "forms": [ - { - "href": "mqtt://iot.eclipse.org/oneOfTest", - "op": "observeproperty", - "mqv:controlPacketValue": "SUBSCRIBE", - "mqv:options": [ - { - "mqv:optionName": "qos", - "mqv:optionValue": 1 - } - ] - }, - { - "href": "mqtt://iot.eclipse.org/oneOfTest", - "op": "unobserveproperty", - "mqv:controlPacketValue": "UNSUBSCRIBE" - } - ] + { + "type": "integer" }, - "maxItemsTest": { - "readOnly": true, - "writeOnly": false, - "type": "array", - "items": { "type": "integer" }, - "maxItems": 5, - "minItems": 2, - "forms": [ - { - "href": "mqtt://iot.eclipse.org/maxItemsTest", - "op": "observeproperty" - }, - { - "href": "mqtt://iot.eclipse.org/maxItemsTest", - "op": "unobserveproperty" - } - ] + { + "type": "null" } - }, - "actions": { - "brewCoffee": { - "input": { - "type": "string", - "enum": ["Espresso", "EspressoDoppio", "Coffee", "HotWater"] - }, - "forms": [ - { - "href": "http://mycoffeemaker.example.com/brewcoffee", - "op": "invokeaction", - "contentType": "application/json" - } - ], - "safe": false, - "idempotent": false - }, - "stopBrewing": { - "forms": [ - { - "href": "coap://mycoffeemaker.example.com/stopbrewing", - "cov:methodName": "GET", - "op": "invokeaction", - "contentType": "application/json" - } - ], - "safe": false, - "idempotent": false + ], + "forms": [ + { + "href": "mqtt://iot.eclipse.org/oneOfTest", + "op": "observeproperty", + "mqv:controlPacketValue": "SUBSCRIBE", + "mqv:options": [ + { + "mqv:optionName": "qos", + "mqv:optionValue": 1 + } + ] }, - "switchOff": { - "forms": [ - { - "href": "http://mycoffeemaker.example.com/switchoff", - "op": "invokeaction", - "contentType": "application/json" - } - ], - "safe": false, - "idempotent": false + { + "href": "mqtt://iot.eclipse.org/oneOfTest", + "op": "unobserveproperty", + "mqv:controlPacketValue": "UNSUBSCRIBE" } + ] }, - "events": { - "waterEmpty": { - "description": "The water fillstate is below a certain level!", - "data": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "forms": [ - { - "href": "http://mycoffeemaker.example.com/waterempty", - "op": "subscribeevent", - "contentType": "application/json", - "subprotocol": "longpoll" - } - ] - }, - "coffeeEmpty": { - "description": "The coffee bean fillstate is below a certain amount!", - "data": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "forms": [ - { - "href": "http://mycoffeemaker.example.com/coffeeempty", - "op": "subscribeevent", - "contentType": "application/json", - "subprotocol": "longpoll" - } - ] + "maxItemsTest": { + "readOnly": true, + "writeOnly": false, + "type": "array", + "items": { "type": "integer" }, + "maxItems": 5, + "minItems": 2, + "forms": [ + { + "href": "mqtt://iot.eclipse.org/maxItemsTest", + "op": "observeproperty" }, - "binFull": { - "description": "The bin fillstate is above a certain level!", - "data": { - "type": "number", - "minimum": 0, - "maximum": 100 - }, - "forms": [ - { - "href": "mqtt://mycoffeemaker.example.com/binfull", - "op": "subscribeevent", - "contentType": "application/json" - } - ] + { + "href": "mqtt://iot.eclipse.org/maxItemsTest", + "op": "unobserveproperty" + } + ] + } + }, + "actions": { + "brewCoffee": { + "input": { + "type": "string", + "enum": ["Espresso", "EspressoDoppio", "Coffee", "HotWater"] + }, + "forms": [ + { + "href": "http://mycoffeemaker.example.com/brewcoffee", + "op": "invokeaction", + "contentType": "application/json" + } + ], + "safe": false, + "idempotent": false + }, + "stopBrewing": { + "forms": [ + { + "href": "coap://mycoffeemaker.example.com/stopbrewing", + "cov:methodName": "GET", + "op": "invokeaction", + "contentType": "application/json" + } + ], + "safe": false, + "idempotent": false + }, + "switchOff": { + "forms": [ + { + "href": "http://mycoffeemaker.example.com/switchoff", + "op": "invokeaction", + "contentType": "application/json" + } + ], + "safe": false, + "idempotent": false + } + }, + "events": { + "waterEmpty": { + "description": "The water fillstate is below a certain level!", + "data": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "forms": [ + { + "href": "http://mycoffeemaker.example.com/waterempty", + "op": "subscribeevent", + "contentType": "application/json", + "subprotocol": "longpoll" + } + ] + }, + "coffeeEmpty": { + "description": "The coffee bean fillstate is below a certain amount!", + "data": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "forms": [ + { + "href": "http://mycoffeemaker.example.com/coffeeempty", + "op": "subscribeevent", + "contentType": "application/json", + "subprotocol": "longpoll" + } + ] + }, + "binFull": { + "description": "The bin fillstate is above a certain level!", + "data": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "forms": [ + { + "href": "mqtt://mycoffeemaker.example.com/binfull", + "op": "subscribeevent", + "contentType": "application/json" } + ] } + } } diff --git a/packages/td_to_asyncapi/index.js b/packages/td_to_asyncapi/index.js index 27c0e823c..a68851881 100644 --- a/packages/td_to_asyncapi/index.js +++ b/packages/td_to_asyncapi/index.js @@ -18,7 +18,7 @@ const genChannels = require("./src/genChannels"); const { genInfo, genTags, genBaseServer } = require("./src/genRoot"); const defaults = require("@thing-description-playground/defaults"); -const {mapSecurity} = require("./src/mapSecurity"); +const { mapSecurity } = require("./src/mapSecurity"); /** * Create an AsyncAPI instance from a Web of Things Thing Description @@ -26,39 +26,41 @@ const {mapSecurity} = require("./src/mapSecurity"); * @returns {Promise<{json:object, yaml:String}>} Resolves as object containing the OAP document or rejects */ function toAsyncAPI(td) { - return new Promise((res, rej) => { - if (typeof td !== "object") { - rej("TD has wrong type, should be an object"); - } - const {securitySchemes, security} = mapSecurity(td.securityDefinitions, td.security); - const components = { - securitySchemes - }; + return new Promise((res, rej) => { + if (typeof td !== "object") { + rej("TD has wrong type, should be an object"); + } + const { securitySchemes, security } = mapSecurity(td.securityDefinitions, td.security); + const components = { + securitySchemes + }; - defaults.addDefaults(td); - - const servers = genBaseServer(td, security); - const asyncApiInstance = new AsyncAPI("2.0.0", genInfo(td), genChannels(td, servers), { - id: td.id, - servers, - tags: genTags(td), - components: components, - externalDocs: new ExternalDocs( - "http://plugfest.thingweb.io/playground/", - "This AsyncAPI instance was generated from a Web of Things (WoT) - Thing Description by the WoT Playground" - ), - }); - - asyncApiInstance.parse().then( - (apiExport) => { - res(apiExport); - }, - (err) => { - console.log(asyncApiInstance.asYaml()); - rej(err); - } - ); + defaults.addDefaults(td); + let securityPara = { + security: security, + }; + const servers = genBaseServer(td, securityPara); + const asyncApiInstance = new AsyncAPI("2.0.0", genInfo(td), genChannels(td, servers), { + id: td.id, + servers, + tags: genTags(td), + components: components, + externalDocs: new ExternalDocs( + "http://plugfest.thingweb.io/playground/", + "This AsyncAPI instance was generated from a Web of Things (WoT) - Thing Description by the WoT Playground" + ), }); + + asyncApiInstance.parse().then( + (apiExport) => { + res(apiExport); + }, + (err) => { + console.log(asyncApiInstance.asYaml()); + rej(err); + } + ); + }); } module.exports = toAsyncAPI; diff --git a/packages/td_to_asyncapi/src/mapSecurity.js b/packages/td_to_asyncapi/src/mapSecurity.js index 15dbe568b..a507d5f84 100644 --- a/packages/td_to_asyncapi/src/mapSecurity.js +++ b/packages/td_to_asyncapi/src/mapSecurity.js @@ -13,7 +13,13 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 */ -module.exports = { mapSecurity, mapSecurityString, mapSecurityDefinitions, mapFormSecurity }; +module.exports = { + mapSecurity, + mapSecurityString, + mapSecurityDefinitions, + hasNoSec, + mapFormSecurity, +}; /** * Convert the TD security Definitions and Security to @@ -22,20 +28,19 @@ module.exports = { mapSecurity, mapSecurityString, mapSecurityDefinitions, mapFo * @param {string|string[]} tdSecurity security scheme names that apply per default */ function mapSecurity(tdDefinitions, tdSecurity) { - const { securitySchemes, scopes } = mapSecurityDefinitions(tdDefinitions); - const security = mapSecurityString(tdSecurity, securitySchemes, scopes); - - if (security.length === 0 && hasNoSec(tdDefinitions, tdSecurity)) { - security.push({}); - } - - for (const key in securitySchemes) { - if (key.includes("combo_sc")) { - delete securitySchemes[key]; - } + const { securitySchemes, scopes } = mapSecurityDefinitions(tdDefinitions); + const security = mapSecurityString(tdSecurity, securitySchemes, scopes); + if (security.length === 0 && hasNoSec(tdDefinitions, tdSecurity)) { + security.push({}); + } + + for (const key in securitySchemes) { + if (key.includes("combo_sc")) { + delete securitySchemes[key]; } + } - return { securitySchemes, security }; + return { securitySchemes, security }; } /** @@ -46,52 +51,54 @@ function mapSecurity(tdDefinitions, tdSecurity) { * @param {string|string[]|undefined} tdFormScopes oauth2 scopes that apply to this form */ function mapFormSecurity(tdDefinitions, tdSecurity, tdFormScopes) { - const { securitySchemes, scopes } = mapSecurityDefinitions(tdDefinitions); - let aapScopes = scopes; - if (typeof tdFormScopes === "string") { - tdFormScopes = [tdFormScopes]; - } - - if (typeof tdFormScopes === "object") { - const scopeSecurities = []; - // filter TD scopes that are not supported by the conversion - aapScopes = {}; - - Object.keys(scopes).forEach((supportedSecurity) => { - scopeSecurities.push(supportedSecurity); - const addScope = tdFormScopes.find((tdFormScope) => - scopes[supportedSecurity].some((supportedScope) => supportedScope === tdFormScope) - ); - if (addScope !== undefined) { - if (aapScopes[supportedSecurity] === undefined) { - aapScopes[supportedSecurity] = [addScope]; - } else { - aapScopes[supportedSecurity].push(addScope); - } - } - }); + const { securitySchemes, scopes } = mapSecurityDefinitions(tdDefinitions); + let aapScopes = scopes; + if (typeof tdFormScopes === "string") { + tdFormScopes = [tdFormScopes]; + } + + if (typeof tdFormScopes === "object") { + const scopeSecurities = []; + // filter TD scopes that are not supported by the conversion + aapScopes = {}; + + Object.keys(scopes).forEach((supportedSecurity) => { + scopeSecurities.push(supportedSecurity); + const addScope = tdFormScopes.find((tdFormScope) => + scopes[supportedSecurity].some( + (supportedScope) => supportedScope === tdFormScope + ) + ); + if (addScope !== undefined) { + if (aapScopes[supportedSecurity] === undefined) { + aapScopes[supportedSecurity] = [addScope]; + } else { + aapScopes[supportedSecurity].push(addScope); + } + } + }); - // add security scheme names if only a scope was given in the TD - scopeSecurities.forEach((scopeSecurity) => { - if (typeof tdSecurity === "string") { - tdSecurity = [tdSecurity]; - } - if (typeof tdSecurity === "object") { - if (!tdSecurity.some((tdSecString) => tdSecString === scopeSecurity)) { - tdSecurity.push(scopeSecurity); - } - } else if (tdSecurity === undefined) { - tdSecurity = scopeSecurities; - } - }); - } - const security = mapSecurityString(tdSecurity, securitySchemes, aapScopes); + // add security scheme names if only a scope was given in the TD + scopeSecurities.forEach((scopeSecurity) => { + if (typeof tdSecurity === "string") { + tdSecurity = [tdSecurity]; + } + if (typeof tdSecurity === "object") { + if (!tdSecurity.some((tdSecString) => tdSecString === scopeSecurity)) { + tdSecurity.push(scopeSecurity); + } + } else if (tdSecurity === undefined) { + tdSecurity = scopeSecurities; + } + }); + } + const security = mapSecurityString(tdSecurity, securitySchemes, aapScopes); - if (security.length === 0 && hasNoSec(tdDefinitions, tdSecurity)) { - security.push({}); - } + if (security.length === 0 && hasNoSec(tdDefinitions, tdSecurity)) { + security.push({}); + } - return { security }; + return { security }; } /** @@ -101,75 +108,81 @@ function mapFormSecurity(tdDefinitions, tdSecurity, tdFormScopes) { * @param {object} tdScopes the found scopes as map {string: string[]} */ function mapSecurityString(tdSecurity, aapSecuritySchemes, tdScopes) { - const aapSecurityContainer = []; - - if (typeof tdSecurity === "string") { - tdSecurity = [tdSecurity]; - } + const aapSecurityContainer = []; + if (typeof tdSecurity === "string") { + tdSecurity = [tdSecurity]; + } + + if (typeof tdSecurity === "object") { + tdSecurity.forEach((tdSecurityKey) => { + let aapSecurity = {}; + const securityObject = aapSecuritySchemes[tdSecurityKey]; + if (securityObject && securityObject.type === "allOf") { + securityObject.secdef.forEach((def) => { + // get scopes + let thisScopes = []; + if (tdScopes[def] !== undefined) { + thisScopes = tdScopes[def]; + } + + const supportedSchemes = Object.keys(aapSecuritySchemes); + + if ( + supportedSchemes.some((supportedScheme) => supportedScheme === def) + ) { + aapSecurity[def] = thisScopes; + } + }); - if (typeof tdSecurity === "object") { - tdSecurity.forEach((tdSecurityKey) => { - let aapSecurity = {}; - const securityObject = aapSecuritySchemes[tdSecurityKey]; - - if (securityObject && securityObject.type === "allOf") { - securityObject.secdef.forEach((def) => { - // get scopes - let thisScopes = []; - if (tdScopes[def] !== undefined) { - thisScopes = tdScopes[def]; - } - - const supportedSchemes = Object.keys(aapSecuritySchemes); - - if (supportedSchemes.some((supportedScheme) => supportedScheme === def)) { - aapSecurity[def] = thisScopes; - } - }); - - if (Object.keys(aapSecurity).length > 0) { - aapSecurityContainer.push(aapSecurity); - } - } else if (securityObject && securityObject.type === "oneOf") { - securityObject.secdef.forEach((def) => { - aapSecurity = {}; - // get scopes - let thisScopes = []; - if (tdScopes[def] !== undefined) { - thisScopes = tdScopes[def]; - } - - const supportedSchemes = Object.keys(aapSecuritySchemes); - - if (supportedSchemes.some((supportedScheme) => supportedScheme === def)) { - aapSecurity[def] = thisScopes; - } - - if (Object.keys(aapSecurity).length > 0) { - aapSecurityContainer.push(aapSecurity); - } - }); - } else { - // get scopes - let thisScopes = []; - if (tdScopes[tdSecurityKey] !== undefined) { - thisScopes = tdScopes[tdSecurityKey]; - } - - const supportedSchemes = Object.keys(aapSecuritySchemes); - - if (supportedSchemes.some((supportedScheme) => supportedScheme === tdSecurityKey)) { - aapSecurity[tdSecurityKey] = thisScopes; - } - - if (Object.keys(aapSecurity).length > 0) { - aapSecurityContainer.push(aapSecurity); - } - } + if (Object.keys(aapSecurity).length > 0) { + aapSecurityContainer.push(aapSecurity); + } + } else if (securityObject && securityObject.type === "oneOf") { + securityObject.secdef.forEach((def) => { + aapSecurity = {}; + // get scopes + let thisScopes = []; + if (tdScopes[def] !== undefined) { + thisScopes = tdScopes[def]; + } + + const supportedSchemes = Object.keys(aapSecuritySchemes); + + if ( + supportedSchemes.some((supportedScheme) => supportedScheme === def) + ) { + aapSecurity[def] = thisScopes; + } + + if (Object.keys(aapSecurity).length > 0) { + aapSecurityContainer.push(aapSecurity); + } }); - } + } else { + // get scopes + let thisScopes = []; + if (tdScopes[tdSecurityKey] !== undefined) { + thisScopes = tdScopes[tdSecurityKey]; + } + + const supportedSchemes = Object.keys(aapSecuritySchemes); + + if ( + supportedSchemes.some( + (supportedScheme) => supportedScheme === tdSecurityKey + ) + ) { + aapSecurity[tdSecurityKey] = thisScopes; + } + + if (Object.keys(aapSecurity).length > 0) { + aapSecurityContainer.push(aapSecurity); + } + } + }); + } - return aapSecurityContainer; + return aapSecurityContainer; } /** @@ -179,24 +192,24 @@ function mapSecurityString(tdSecurity, aapSecuritySchemes, tdScopes) { * @param {object|undefined} tdDefinitions if no object is given, empty securitySchemes and scopes are returned */ function mapSecurityDefinitions(tdDefinitions) { - const securitySchemes = {}; - const scopes = {}; - - if (typeof tdDefinitions === "object") { - Object.keys(tdDefinitions).forEach((key) => { - if (typeof tdDefinitions[key].scheme === "string") { - const { aapDefinition, scope } = genaapDefinition(tdDefinitions[key]); - if (Object.keys(aapDefinition).length > 0) { - securitySchemes[key] = aapDefinition; - if (typeof scope === "object") { - scopes[key] = scope; - } - } - } - }); - } + const securitySchemes = {}; + const scopes = {}; + + if (typeof tdDefinitions === "object") { + Object.keys(tdDefinitions).forEach((key) => { + if (typeof tdDefinitions[key].scheme === "string") { + const { aapDefinition, scope } = genaapDefinition(tdDefinitions[key]); + if (Object.keys(aapDefinition).length > 0) { + securitySchemes[key] = aapDefinition; + if (typeof scope === "object") { + scopes[key] = scope; + } + } + } + }); + } - return { securitySchemes, scopes }; + return { securitySchemes, scopes }; } /** @@ -204,19 +217,23 @@ function mapSecurityDefinitions(tdDefinitions) { * @param {object} tdDefinition one security definition object */ function genaapDefinition(tdDefinition) { - const aapDefinition = {}; - const tdScheme = tdDefinition.scheme.toLowerCase(); - let addOptionals = true; - const httpSchemes = ["basic", "digest", "bearer"]; - let scope; - - if (httpSchemes.some((httpScheme) => httpScheme === tdScheme)) { - aapDefinition.type = "http"; - aapDefinition.scheme = tdScheme; - if (tdDefinition.in && tdDefinition.in !== "header") { - throw new Error("Cannot represent " + tdScheme + " authentication outside the header in AsyncAPI"); - } + const aapDefinition = {}; + const tdScheme = tdDefinition.scheme.toLowerCase(); + let addOptionals = true; + const httpSchemes = ["basic", "digest", "bearer"]; + let scope; + + if (httpSchemes.some((httpScheme) => httpScheme === tdScheme)) { + aapDefinition.type = "http"; + aapDefinition.scheme = tdScheme; + if (tdDefinition.in && tdDefinition.in !== "header") { + throw new Error( + "Cannot represent " + + tdScheme + + " authentication outside the header in AsyncAPI" + ); } + } switch (tdScheme) { case "nosec": @@ -233,88 +250,99 @@ function genaapDefinition(tdDefinition) { // do nothing? break;*/ - case "digest": - aapDefinition["x-qop"] = (tdDefinition.qop === undefined) ? "auth" : tdDefinition.qop; - if (tdDefinition.name) { - aapDefinition["x-name"] = tdDefinition.name; - } - // should the x-in parameter be added if in = header? - if (tdDefinition.in) { - aapDefinition["x-in"] = tdDefinition.in; - } - break + case "digest": + aapDefinition["x-qop"] = + tdDefinition.qop === undefined ? "auth" : tdDefinition.qop; + if (tdDefinition.name) { + aapDefinition["x-name"] = tdDefinition.name; + } + // should the x-in parameter be added if in = header? + if (tdDefinition.in) { + aapDefinition["x-in"] = tdDefinition.in; + } + break; - case "bearer": - aapDefinition.bearerFormat = (tdDefinition.format === undefined) ? "jwt" : tdDefinition.format; - aapDefinition["x-alg"] = (tdDefinition.alg === undefined) ? "ES256" : tdDefinition.alg; - if (tdDefinition.name) { - aapDefinition["x-name"] = tdDefinition.name; - } - // should the x-in parameter be added if in = header? - if (tdDefinition.in) { - aapDefinition["x-in"] = tdDefinition.in; - } - if (tdDefinition.authorization) { - aapDefinition["x-authorization"] = tdDefinition.authorization; - } - break + case "bearer": + aapDefinition.bearerFormat = + tdDefinition.format === undefined ? "jwt" : tdDefinition.format; + aapDefinition["x-alg"] = + tdDefinition.alg === undefined ? "ES256" : tdDefinition.alg; + if (tdDefinition.name) { + aapDefinition["x-name"] = tdDefinition.name; + } + // should the x-in parameter be added if in = header? + if (tdDefinition.in) { + aapDefinition["x-in"] = tdDefinition.in; + } + if (tdDefinition.authorization) { + aapDefinition["x-authorization"] = tdDefinition.authorization; + } + break; - case "apikey": - aapDefinition.type = "httpApiKey"; - aapDefinition.in = tdDefinition.in === undefined ? "query" : tdDefinition.in; - aapDefinition.name = (tdDefinition.name === undefined) ? "UNKNOWN" : tdDefinition.name; - if (aapDefinition.in != "query" && aapDefinition.in != "header" && aapDefinition.in != "cookie") { - throw new Error("Cannot represent ApiKey in" + aapDefinition.in +" with AsyncAPI"); - } - break + case "apikey": + aapDefinition.type = "httpApiKey"; + aapDefinition.in = + tdDefinition.in === undefined ? "query" : tdDefinition.in; + aapDefinition.name = + tdDefinition.name === undefined ? "UNKNOWN" : tdDefinition.name; + if ( + aapDefinition.in != "query" && + aapDefinition.in != "header" && + aapDefinition.in != "cookie" + ) { + throw new Error( + "Cannot represent ApiKey in" + aapDefinition.in + " with AsyncAPI" + ); + } + break; + + case "oauth2": + if (typeof tdDefinition.scopes === "string") { + scope = [tdDefinition.scopes]; + } else if (typeof tdDefinition.scopes === "object") { + scope = tdDefinition.scopes; + } - case "oauth2": - if (typeof tdDefinition.scopes === "string") { - scope = [tdDefinition.scopes]; - } else if (typeof tdDefinition.scopes === "object") { - scope = tdDefinition.scopes; - } - - aapDefinition.type = "oauth2"; - aapDefinition.flows = genOAuthFlows(tdDefinition); - break; - /* + aapDefinition.type = "oauth2"; + aapDefinition.flows = genOAuthFlows(tdDefinition); + break; + /* case "openidconnect": aapDefinition.type = "openIdConnect" aapDefinition.openIdConnectUrl = (tdDefinition.openIdConnectUrl === undefined) ? "UNKNOWN" : tdDefinition.openIdConnectUrl //aapDefinition.flows = genOAuthFlows(tdDefinition) break */ - case "combo": - // todo Implement combo security scheme - if (tdDefinition.allOf) { - aapDefinition.type = "allOf"; - aapDefinition.secdef = tdDefinition.allOf; - } else { - aapDefinition.type = "oneOf"; - aapDefinition.secdef = tdDefinition.oneOf; - } - break; - - default: - console.log("unknown security definition: " + tdScheme); - addOptionals = false; - } + case "combo": + // todo Implement combo security scheme + if (tdDefinition.allOf) { + aapDefinition.type = "allOf"; + aapDefinition.secdef = tdDefinition.allOf; + } else { + aapDefinition.type = "oneOf"; + aapDefinition.secdef = tdDefinition.oneOf; + } + break; - // add optional fields - if (addOptionals) { - if (tdDefinition.description) { - aapDefinition.description = tdDefinition.description; - } - if (tdDefinition.descriptions) { - aapDefinition["x-descriptions"] = tdDefinition.descriptions; - } - if (tdDefinition.proxy) { - aapDefinition["x-proxy"] = tdDefinition.proxy; - } + default: + console.log("unknown security definition: " + tdScheme); + addOptionals = false; + } + + // add optional fields + if (addOptionals) { + if (tdDefinition.description) { + aapDefinition.description = tdDefinition.description; + } + if (tdDefinition.descriptions) { + aapDefinition["x-descriptions"] = tdDefinition.descriptions; } + if (tdDefinition.proxy) { + aapDefinition["x-proxy"] = tdDefinition.proxy; + } + } - return { aapDefinition, scope }; + return { aapDefinition, scope }; } /** @@ -322,62 +350,73 @@ function genaapDefinition(tdDefinition) { * @param {object} tdDefinition The security definition object of a TD */ function genOAuthFlows(tdDefinition) { - const aapFlow = {}; - if (typeof tdDefinition.flow !== "string") { - throw new Error("the oauth2 object has no flow of type string"); - } - - const tdFlow = tdDefinition.flow.toLowerCase(); - const mapTdToAap = { - implicit: ["implicit"], - password: ["password", "ropc"], - clientCredentials: ["application", "client", "clientcredentials", "clientcredential"], - authorizationCode: ["accesscode", "code", "authorizationcode"], - "x-device": ["device"], - }; - - Object.keys(mapTdToAap).forEach((key) => { - if (mapTdToAap[key].some((arrayElement) => arrayElement === tdFlow)) { - const protoFlow = {}; - if (key === "implicit" || key === "authorizationCode" || key === "x-device") { - if (tdDefinition.authorization === undefined) { - throw new Error("the authorization URI is required for oauth2 flow: " + key); - } else { - protoFlow.authorizationUrl = tdDefinition.authorization; - } - } - if ( - key === "password" || - key === "clientCredentials" || - key === "authorizationCode" || - key === "x-device" - ) { - if (tdDefinition.token === undefined) { - throw new Error("the token URI is required for oauth2 flow: " + key); - } else { - protoFlow.tokenUrl = tdDefinition.token; - } - } - if (typeof tdDefinition.refresh === "string") { - protoFlow.refreshUrl = tdDefinition.refresh; - } - if (tdDefinition.scopes === undefined) { - protoFlow.scopes = { - /* "default": "autogenerated default scope" */ - }; // TODO: is one scope required? (I don't think so) - } else { - protoFlow.scopes = {}; - if (typeof tdDefinition.scopes === "string") { - tdDefinition.scopes = [tdDefinition.scopes]; - } - tdDefinition.scopes.forEach((scope) => { - protoFlow.scopes[scope] = ""; - }); - } - aapFlow[key] = protoFlow; + const aapFlow = {}; + if (typeof tdDefinition.flow !== "string") { + throw new Error("the oauth2 object has no flow of type string"); + } + + const tdFlow = tdDefinition.flow.toLowerCase(); + const mapTdToAap = { + implicit: ["implicit"], + password: ["password", "ropc"], + clientCredentials: [ + "application", + "client", + "clientcredentials", + "clientcredential", + ], + authorizationCode: ["accesscode", "code", "authorizationcode"], + "x-device": ["device"], + }; + + Object.keys(mapTdToAap).forEach((key) => { + if (mapTdToAap[key].some((arrayElement) => arrayElement === tdFlow)) { + const protoFlow = {}; + if ( + key === "implicit" || + key === "authorizationCode" || + key === "x-device" + ) { + if (tdDefinition.authorization === undefined) { + throw new Error( + "the authorization URI is required for oauth2 flow: " + key + ); + } else { + protoFlow.authorizationUrl = tdDefinition.authorization; } - }); - return aapFlow; + } + if ( + key === "password" || + key === "clientCredentials" || + key === "authorizationCode" || + key === "x-device" + ) { + if (tdDefinition.token === undefined) { + throw new Error("the token URI is required for oauth2 flow: " + key); + } else { + protoFlow.tokenUrl = tdDefinition.token; + } + } + if (typeof tdDefinition.refresh === "string") { + protoFlow.refreshUrl = tdDefinition.refresh; + } + if (tdDefinition.scopes === undefined) { + protoFlow.scopes = { + /* "default": "autogenerated default scope" */ + }; // TODO: is one scope required? (I don't think so) + } else { + protoFlow.scopes = {}; + if (typeof tdDefinition.scopes === "string") { + tdDefinition.scopes = [tdDefinition.scopes]; + } + tdDefinition.scopes.forEach((scope) => { + protoFlow.scopes[scope] = ""; + }); + } + aapFlow[key] = protoFlow; + } + }); + return aapFlow; } /** @@ -386,30 +425,32 @@ function genOAuthFlows(tdDefinition) { * @param {string|string[]} tdSecurity security scheme names that apply to this TD part */ function hasNoSec(tdDefinitions, tdSecurity) { - let foundNoSec = false; - - // find all noSec names - const noSecNames = []; - if (typeof tdDefinitions === "object") { - Object.keys(tdDefinitions).forEach((key) => { - const tdScheme = tdDefinitions[key].scheme; - if (typeof tdScheme === "string" && tdScheme.toLowerCase() === "nosec") { - noSecNames.push(key); - } - }); - } - - if (typeof tdSecurity === "string") { - tdSecurity = [tdSecurity]; - } - if (typeof tdSecurity === "undefined") { - tdSecurity = []; - } - - // check if all security Schemes are of type noSec - if (tdSecurity.length > 0) { - foundNoSec = tdSecurity.every((securityString) => noSecNames.some((noSecName) => noSecName === securityString)); - } - - return foundNoSec; + let foundNoSec = false; + + // find all noSec names + const noSecNames = []; + if (typeof tdDefinitions === "object") { + Object.keys(tdDefinitions).forEach((key) => { + const tdScheme = tdDefinitions[key].scheme; + if (typeof tdScheme === "string" && tdScheme.toLowerCase() === "nosec") { + noSecNames.push(key); + } + }); + } + + if (typeof tdSecurity === "string") { + tdSecurity = [tdSecurity]; + } + if (typeof tdSecurity === "undefined") { + tdSecurity = []; + } + + // check if all security Schemes are of type noSec + if (tdSecurity.length > 0) { + foundNoSec = tdSecurity.every((securityString) => + noSecNames.some((noSecName) => noSecName === securityString) + ); + } + + return foundNoSec; }