diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/bindings/AsyncApiCommonBindingEmitter.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/bindings/AsyncApiCommonBindingEmitter.scala index 5919481f4e..d287ac28e2 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/bindings/AsyncApiCommonBindingEmitter.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/bindings/AsyncApiCommonBindingEmitter.scala @@ -1,6 +1,5 @@ package amf.apicontract.internal.spec.async.emitters.bindings -import amf.core.internal.annotations.SynthesizedField import amf.core.internal.parser.domain.Fields import amf.core.internal.render.BaseEmitters.ValueEmitter import amf.core.internal.render.emitters.EntryEmitter @@ -8,11 +7,11 @@ import amf.apicontract.internal.metamodel.domain.bindings.BindingVersion import scala.collection.mutable.ListBuffer -abstract class AsyncApiCommonBindingEmitter() extends EntryEmitter { +abstract class AsyncApiCommonBindingEmitter extends EntryEmitter { def emitBindingVersion(fs: Fields, result: ListBuffer[EntryEmitter]): Unit = { fs.entry(BindingVersion.BindingVersion).foreach { f => - if (!f.value.annotations.contains(classOf[SynthesizedField])) result += ValueEmitter("bindingVersion", f) + if (!f.value.annotations.isSynthesized) result += ValueEmitter("bindingVersion", f) } } } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/domain/AsyncApiOperationEmitter.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/domain/AsyncApiOperationEmitter.scala index 20d436e1b1..8ec822d760 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/domain/AsyncApiOperationEmitter.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/emitters/domain/AsyncApiOperationEmitter.scala @@ -3,6 +3,7 @@ package amf.apicontract.internal.spec.async.emitters.domain import amf.apicontract.client.scala.model.domain.{Operation, Tag} import amf.apicontract.internal.metamodel.domain.OperationModel import amf.apicontract.internal.spec.async.AsyncHelper +import amf.apicontract.internal.spec.common.emitter.SecurityRequirementsEmitter import amf.apicontract.internal.spec.oas.emitter.context.OasLikeSpecEmitterContext import amf.apicontract.internal.spec.oas.emitter.domain.{ OasLikeOperationEmitter, @@ -52,6 +53,11 @@ case class AsyncOperationPartEmitter(operation: Operation, isTrait: Boolean, ord emitMessage(fs).map(reqOrRes => tempResult += reqOrRes) fs.entry(OperationModel.Extends).foreach(f => emitTraits(f, tempResult)) } + fs.entry(OperationModel.Security) + .foreach(f => + if (!f.value.annotations.isSynthesized) + tempResult += SecurityRequirementsEmitter("security", f, ordering) + ) traverse(ordering.sorted(super.commonEmitters ++ tempResult), eb) } } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/AsyncSpecAwareContext.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/AsyncSpecAwareContext.scala index 0c0b57e62d..6feecb5bbc 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/AsyncSpecAwareContext.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/AsyncSpecAwareContext.scala @@ -34,7 +34,7 @@ class Async20VersionFactory()(implicit ctx: AsyncWebApiContext) extends AsyncSpe override def serverVariableParser(entry: YMapEntry, parent: String): OasLikeServerVariableParser = Async20ServerVariableParser(YMapEntryLike(entry), parent)(ctx) override def operationParser(entry: YMapEntry, adopt: Operation => Operation): OasLikeOperationParser = - AsyncOperationParser(entry, adopt)(ctx) + Async20OperationParser(entry, adopt)(ctx) override def endPointParser(entry: YMapEntry, parentId: String, collector: List[EndPoint]): OasLikeEndpointParser = new Async20EndpointParser(entry, parentId, collector)(ctx) override def securitySchemeParser: (YMapEntryLike, SecurityScheme => SecurityScheme) => SecuritySchemeParser = @@ -48,7 +48,8 @@ class Async20VersionFactory()(implicit ctx: AsyncWebApiContext) extends AsyncSpe parent: String, messageType: Option[MessageType], isTrait: Boolean = false - )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = Async20MessageParser(entryLike, parent, messageType, isTrait) + )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = + Async20MessageParser(entryLike, parent, messageType, isTrait) } object Async20VersionFactory { @@ -61,7 +62,8 @@ class Async21VersionFactory()(implicit ctx: AsyncWebApiContext) extends Async20V parent: String, messageType: Option[MessageType], isTrait: Boolean - )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = Async21MessageParser(entryLike, parent, messageType, isTrait) + )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = + Async21MessageParser(entryLike, parent, messageType, isTrait) } object Async21VersionFactory { @@ -87,23 +89,27 @@ object Async23VersionFactory { def apply()(implicit ctx: AsyncWebApiContext): Async23VersionFactory = new Async23VersionFactory()(ctx) } -class Async24VersionFactory()(implicit ctx: AsyncWebApiContext) extends Async23VersionFactory{ +class Async24VersionFactory()(implicit ctx: AsyncWebApiContext) extends Async23VersionFactory { override def messageParser( entryLike: YMapEntryLike, parent: String, messageType: Option[MessageType], isTrait: Boolean - )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = Async24MessageParser(entryLike, parent, messageType, isTrait) + )(implicit ctx: AsyncWebApiContext): AsyncMessageParser = + Async24MessageParser(entryLike, parent, messageType, isTrait) override def serverVariableParser(entry: YMapEntry, parent: String): OasLikeServerVariableParser = - new Async24ServerVariableParser(YMapEntryLike(entry), parent)(ctx) + new Async24ServerVariableParser(YMapEntryLike(entry), parent)(ctx) + + override def operationParser(entry: YMapEntry, adopt: Operation => Operation): OasLikeOperationParser = + Async24OperationParser(entry, adopt)(ctx) } object Async24VersionFactory { def apply()(implicit ctx: AsyncWebApiContext): Async24VersionFactory = new Async24VersionFactory()(ctx) } -class Async25VersionFactory(implicit ctx: AsyncWebApiContext) extends Async24VersionFactory { +class Async25VersionFactory(implicit ctx: AsyncWebApiContext) extends Async24VersionFactory { override def serversParser(map: YMap, api: AsyncApi): AsyncServersParser = new Async25ServersParser(map, api) } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/syntax/Async24Syntax.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/syntax/Async24Syntax.scala index 69a7e2e661..3c99b941f2 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/syntax/Async24Syntax.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/context/syntax/Async24Syntax.scala @@ -8,7 +8,10 @@ object Async24Syntax extends SpecSyntax { Async23Syntax.nodes, "message" -> Set("messageId"), "components" -> Set( - "serverVariables", + "serverVariables" + ), + "operation" -> Set( + "security" + ) ) - ) } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/Async20EndpointParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/Async20EndpointParser.scala index 960878a199..bac596b415 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/Async20EndpointParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/Async20EndpointParser.scala @@ -1,6 +1,6 @@ package amf.apicontract.internal.spec.async.parser.domain -import amf.apicontract.client.scala.model.domain.{EndPoint, Operation, Server} +import amf.apicontract.client.scala.model.domain.{EndPoint, Server} import amf.apicontract.internal.metamodel.domain.{EndPointModel, ServerModel} import amf.apicontract.internal.spec.async.parser.bindings.AsyncChannelBindingsParser import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext @@ -14,8 +14,6 @@ import amf.core.internal.validation.CoreValidations import amf.shapes.internal.spec.common.parser.{AnnotationParser, YMapEntryLike} import org.yaml.model.{YMap, YMapEntry, YNode, YSequence} -import scala.collection.mutable - class Async20EndpointParser(entry: YMapEntry, parentId: String, collector: List[EndPoint])( override implicit val ctx: AsyncWebApiContext ) extends OasLikeEndpointParser(entry, parentId, collector) { @@ -50,11 +48,7 @@ class Async20EndpointParser(entry: YMapEntry, parentId: String, collector: List[ map.regex( "subscribe|publish", entries => { - val operations = mutable.ListBuffer[Operation]() - entries.foreach { entry => - val operationParser = ctx.factory.operationParser(entry, (o: Operation) => o) - operations += operationParser.parse() - } + val operations = parseOperations(entries) endpoint.setWithoutId(EndPointModel.Operations, AmfArray(operations, Annotations(map)), Annotations(map)) } ) diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncOperationParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncOperationParser.scala index 37bd62824e..0767c9a34b 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncOperationParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncOperationParser.scala @@ -17,12 +17,20 @@ import amf.core.internal.validation.CoreValidations import amf.shapes.internal.spec.common.parser.{AnnotationParser, YMapEntryLike} import org.yaml.model._ -object AsyncOperationParser { +object Async20OperationParser { def apply(entry: YMapEntry, adopt: Operation => Operation, isTrait: Boolean = false)(implicit ctx: AsyncWebApiContext ): AsyncOperationParser = - if (isTrait) new AsyncOperationTraitParser(entry, adopt) - else new AsyncConcreteOperationParser(entry, adopt) + if (isTrait) new Async20OperationTraitParser(entry, adopt) + else new Async20ConcreteOperationParser(entry, adopt) +} + +object Async24OperationParser { + def apply(entry: YMapEntry, adopt: Operation => Operation, isTrait: Boolean = false)(implicit + ctx: AsyncWebApiContext + ): AsyncOperationParser = + if (isTrait) Async24OperationTraitParser(entry, adopt) + else Async24ConcreteOperationParser(entry, adopt) } abstract class AsyncOperationParser(entry: YMapEntry, adopt: Operation => Operation)( @@ -52,6 +60,8 @@ abstract class AsyncOperationParser(entry: YMapEntry, adopt: Operation => Operat parseTraits(map, operation) + map.key("security").foreach(entry => parseSecuritySchemas(entry, operation)) + operation } @@ -59,12 +69,14 @@ abstract class AsyncOperationParser(entry: YMapEntry, adopt: Operation => Operat map.key("operationId", OperationModel.OperationId in operation) } - protected def parseMessages(map: YMap, operation: Operation) + protected def parseMessages(map: YMap, operation: Operation): Unit = {} + + protected def parseTraits(map: YMap, operation: Operation): Unit = {} - protected def parseTraits(map: YMap, operation: Operation) + protected def parseSecuritySchemas(entry: YMapEntry, operation: Operation): Unit = {} } -private class AsyncConcreteOperationParser(entry: YMapEntry, adopt: Operation => Operation)(implicit +class Async20ConcreteOperationParser(entry: YMapEntry, adopt: Operation => Operation)(implicit ctx: AsyncWebApiContext ) extends AsyncOperationParser(entry, adopt) { @@ -92,9 +104,11 @@ private class AsyncConcreteOperationParser(entry: YMapEntry, adopt: Operation => ) } ) + + override protected def parseSecuritySchemas(entry: YMapEntry, operation: Operation): Unit = {} } -private class AsyncOperationTraitParser(entry: YMapEntry, adopt: Operation => Operation)( +class Async20OperationTraitParser(entry: YMapEntry, adopt: Operation => Operation)( override implicit val ctx: AsyncWebApiContext ) extends AsyncOperationParser(entry, adopt) { @@ -113,9 +127,11 @@ private class AsyncOperationTraitParser(entry: YMapEntry, adopt: Operation => Op } } - override protected def parseMessages(map: YMap, operation: Operation): Unit = Unit + override protected def parseMessages(map: YMap, operation: Operation): Unit = {} + + override protected def parseTraits(map: YMap, operation: Operation): Unit = {} - override protected def parseTraits(map: YMap, operation: Operation): Unit = Unit + override protected def parseSecuritySchemas(entry: YMapEntry, operation: Operation): Unit = {} } case class AsyncOperationTraitRefParser(node: YNode, adopt: Operation => Operation, name: Option[String] = None)( @@ -162,9 +178,43 @@ case class AsyncOperationTraitRefParser(node: YNode, adopt: Operation => Operati ctx.navigateToRemoteYNode(url) match { case Some(result) => val operationNode = result.remoteNode - AsyncOperationParser(YMapEntry(name.getOrElse(url), operationNode), adopt, isTrait = true)(result.context) + Async20OperationParser(YMapEntry(name.getOrElse(url), operationNode), adopt, isTrait = true)(result.context) .parse() case None => linkError(url, node) } } } + +case class Async24ConcreteOperationParser(entry: YMapEntry, adopt: Operation => Operation)( + override implicit val ctx: AsyncWebApiContext +) extends Async20ConcreteOperationParser(entry, adopt) + with SecuritySchemeParser { + + override protected def parseMessages(map: YMap, operation: Operation): Unit = + super.parseMessages(map: YMap, operation: Operation) + + override protected def parseTraits(map: YMap, operation: Operation): Unit = + super.parseTraits(map: YMap, operation: Operation) + + override protected def parseSecuritySchemas(entry: YMapEntry, operation: Operation): Unit = { + super.parseSecuritySchemas(entry, operation) + parseSecurityScheme(entry, OperationModel.Security, operation) + } +} + +case class Async24OperationTraitParser(entry: YMapEntry, adopt: Operation => Operation)( + override implicit val ctx: AsyncWebApiContext +) extends Async20OperationTraitParser(entry, adopt) + with SecuritySchemeParser { + + override protected def parseMessages(map: YMap, operation: Operation): Unit = + super.parseMessages(map: YMap, operation: Operation) + + override protected def parseTraits(map: YMap, operation: Operation): Unit = + super.parseTraits(map: YMap, operation: Operation) + + override protected def parseSecuritySchemas(entry: YMapEntry, operation: Operation): Unit = { + super.parseSecuritySchemas(entry, operation) + parseSecurityScheme(entry, OperationModel.Security, operation) + } +} diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncServerParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncServerParser.scala index c782eb7a44..5366d46258 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncServerParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/AsyncServerParser.scala @@ -2,18 +2,15 @@ package amf.apicontract.internal.spec.async.parser.domain import amf.apicontract.client.scala.model.domain.{Server, Tag} import amf.apicontract.client.scala.model.domain.api.AsyncApi -import amf.apicontract.client.scala.model.domain.security.SecurityRequirement import amf.apicontract.internal.metamodel.domain.ServerModel import amf.apicontract.internal.spec.async.parser.bindings.AsyncServerBindingsParser import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext import amf.apicontract.internal.spec.common.WebApiDeclarations.ErrorServer -import amf.apicontract.internal.spec.common.parser.OasLikeSecurityRequirementParser import amf.apicontract.internal.spec.oas.parser.domain.{OasLikeServerParser, TagsParser} import amf.apicontract.internal.spec.spec.OasDefinitions import amf.core.client.scala.model.domain.{AmfArray, AmfScalar} import amf.core.internal.parser.YMapOps import amf.core.internal.parser.domain.{Annotations, ScalarNode, SearchScope} -import amf.core.internal.utils.IdCounter import amf.core.internal.validation.CoreValidations import amf.shapes.internal.spec.common.parser.{AnnotationParser, YMapEntryLike} import org.yaml.model.{YMap, YNode} @@ -49,14 +46,16 @@ class Async23ServersParser(map: YMap, api: AsyncApi)( new Async23ServerParser(api.id, entryLike) } -class Async25ServersParser(map: YMap, api: AsyncApi)(override implicit val ctx: AsyncWebApiContext) extends AsyncServersParser(map, api){ +class Async25ServersParser(map: YMap, api: AsyncApi)(override implicit val ctx: AsyncWebApiContext) + extends AsyncServersParser(map, api) { override protected def serverParser(entryLike: YMapEntryLike): OasLikeServerParser = new Async25SeverParser(api.id, entryLike) } class Async20ServerParser(parent: String, entryLike: YMapEntryLike)(implicit override val ctx: AsyncWebApiContext -) extends OasLikeServerParser(parent, entryLike) { +) extends OasLikeServerParser(parent, entryLike) + with SecuritySchemeParser { override def parse(): Server = { val server = super.parse() @@ -71,14 +70,7 @@ class Async20ServerParser(parent: String, entryLike: YMapEntryLike)(implicit map.key( "security", - entry => { - val idCounter = new IdCounter() - val securedBy = entry.value - .as[Seq[YNode]] - .flatMap(s => OasLikeSecurityRequirementParser(s, (_: SecurityRequirement) => Unit, idCounter).parse()) - - server.setWithoutId(ServerModel.Security, AmfArray(securedBy, Annotations(entry.value)), Annotations(entry)) - } + entry => parseSecurityScheme(entry, ServerModel.Security, server) ) server @@ -143,10 +135,10 @@ class Async23ServerParser(parent: String, entryLike: YMapEntryLike)(implicit ove } class Async25SeverParser(parent: String, entryLike: YMapEntryLike)(implicit override val ctx: AsyncWebApiContext) - extends Async23ServerParser(parent, entryLike){ + extends Async23ServerParser(parent, entryLike) { override def parse(): Server = { val server = super.parse() - map.key("tags").foreach{ entry => + map.key("tags").foreach { entry => val tags = entry.value.as[Seq[YMap]].map(tag => TagsParser(tag, (tag: Tag) => tag).parse()) server.setWithoutId(ServerModel.Tags, AmfArray(tags, Annotations(entry.value)), Annotations(entry)) } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/SecuritySchemeParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/SecuritySchemeParser.scala new file mode 100644 index 0000000000..7489db1d6d --- /dev/null +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/SecuritySchemeParser.scala @@ -0,0 +1,20 @@ +package amf.apicontract.internal.spec.async.parser.domain + +import amf.apicontract.client.scala.model.domain.security.SecurityRequirement +import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext +import amf.apicontract.internal.spec.common.parser.OasLikeSecurityRequirementParser +import amf.core.client.scala.model.domain.{AmfArray, AmfObject} +import amf.core.internal.metamodel.Field +import amf.core.internal.parser.domain.Annotations +import amf.core.internal.utils.IdCounter +import org.yaml.model.{YMapEntry, YNode} + +trait SecuritySchemeParser { + def parseSecurityScheme(entry: YMapEntry, field: Field, parent: AmfObject)(implicit ctx: AsyncWebApiContext): Unit = { + val idCounter = new IdCounter() + val securedBy = entry.value + .as[Seq[YNode]] + .flatMap(s => OasLikeSecurityRequirementParser(s, (_: SecurityRequirement) => Unit, idCounter).parse()) + parent.setWithoutId(field, AmfArray(securedBy, Annotations(entry.value)), Annotations(entry)) + } +} diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/declarations/Async20DeclarationParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/declarations/Async20DeclarationParser.scala index d9156af8bf..e8594c959d 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/declarations/Async20DeclarationParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/async/parser/domain/declarations/Async20DeclarationParser.scala @@ -24,7 +24,7 @@ import amf.apicontract.internal.spec.async.parser.bindings.{ import amf.apicontract.internal.spec.async.parser.context.AsyncWebApiContext import amf.apicontract.internal.spec.async.parser.domain.{ AsyncCorrelationIdParser, - AsyncOperationParser, + Async20OperationParser, AsyncParametersParser } import amf.apicontract.internal.spec.oas.parser.document.OasLikeDeclarationsHelper @@ -81,7 +81,7 @@ case class Async20DeclarationParser() extends AsyncDeclarationParser with OasLik addDeclarationKey(DeclarationKey(entry, isAbstract = true)) entry.value.as[YMap].entries.foreach { entry => val adopt = (o: Operation) => o - val operation = AsyncOperationParser(entry, adopt, isTrait = true).parse() + val operation = Async20OperationParser(entry, adopt, isTrait = true).parse() operation.add(DeclaredElement()) ctx.declarations += operation } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/emitter/AbstractSecurityRequirementEmitter.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/emitter/AbstractSecurityRequirementEmitter.scala index 88505694f2..b58cf86f1d 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/emitter/AbstractSecurityRequirementEmitter.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/emitter/AbstractSecurityRequirementEmitter.scala @@ -41,7 +41,7 @@ case class OasWithExtensionsSecurityRequirementsEmitter(key: String, f: FieldEnt ) } - def allSchemesAreValidInOas(schemes: Seq[ParametrizedSecurityScheme], spec: Spec): Boolean = { + private def allSchemesAreValidInOas(schemes: Seq[ParametrizedSecurityScheme], spec: Spec): Boolean = { schemes.forall(s => { s.scheme match { case linkable: Linkable if linkable.isLink => return true diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/transformation/stage/OperationsSecurityResolutionStage.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/transformation/stage/OperationsSecurityResolutionStage.scala new file mode 100644 index 0000000000..1e8615d4bc --- /dev/null +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/common/transformation/stage/OperationsSecurityResolutionStage.scala @@ -0,0 +1,61 @@ +package amf.apicontract.internal.spec.common.transformation.stage + +import amf.apicontract.client.scala.model.domain.api.AsyncApi +import amf.apicontract.client.scala.model.domain.{Operation, Server} +import amf.apicontract.internal.metamodel.domain.OperationModel +import amf.core.client.common.validation.{Async20Profile, ProfileName} +import amf.core.client.scala.AMFGraphConfiguration +import amf.core.client.scala.errorhandling.AMFErrorHandler +import amf.core.client.scala.model.document.{BaseUnit, Document} +import amf.core.client.scala.model.domain.AmfArray +import amf.core.client.scala.transform.TransformationStep +import amf.core.internal.parser.domain.Annotations + +/** Places all available security schemes in all operations unless an operation defines a subset of those */ +class OperationsSecurityResolutionStage(profile: ProfileName, val keepEditingInfo: Boolean = false) + extends TransformationStep() { + + override def transform( + model: BaseUnit, + errorHandler: AMFErrorHandler, + configuration: AMFGraphConfiguration + ): BaseUnit = { + profile match { + case Async20Profile => resolveOperationSecuritySchemas(model) + case _ => model + } + } + + private def resolveOperationSecuritySchemas(unit: BaseUnit): BaseUnit = { + unit match { + case doc: Document if doc.encodes.isInstanceOf[AsyncApi] => + val asyncApi = doc.encodes.asInstanceOf[AsyncApi] + asyncApi.endPoints.foreach { endpoint => + endpoint.operations.foreach(resolveOperationSecurity(_, endpoint.servers)) + } + doc + case _ => unit + } + } + + private def resolveOperationSecurity( + operation: Operation, + endpointServers: Seq[Server] + ): Unit = { + val securities = endpointServers.flatMap(_.security) + operation.fields.?[AmfArray](OperationModel.Security) match { + // security keyword not defined, channel has every security that's available in every server it applies + case None => + if (securities.nonEmpty) + operation.setArrayWithoutId(OperationModel.Security, securities, Annotations.synthesized()) + + // security keyword declared with an empty list `servers: []`, same as above + case Some(array: AmfArray) if array.values.isEmpty => + if (securities.nonEmpty) + operation.setArrayWithoutId(OperationModel.Security, securities, Annotations.synthesized()) + + // security keyword defined with a list of security schemas from parsing, do nothing + case _ => // ignore + } + } +} diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/context/OasLikeWebApiContext.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/context/OasLikeWebApiContext.scala index 887186ceb9..3310d7e9ff 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/context/OasLikeWebApiContext.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/context/OasLikeWebApiContext.scala @@ -1,6 +1,5 @@ package amf.apicontract.internal.spec.oas.parser.context -import amf.aml.internal.semantic.SemanticExtensionsFacadeBuilder import amf.apicontract.client.scala.model.domain.security.SecurityScheme import amf.apicontract.client.scala.model.domain.{EndPoint, Operation} import amf.apicontract.internal.spec.common.OasLikeWebApiDeclarations @@ -16,8 +15,8 @@ import amf.core.client.scala.config.ParsingOptions import amf.core.client.scala.model.document.ExternalFragment import amf.core.client.scala.model.domain.Shape import amf.core.client.scala.parse.document.{ParsedReference, ParserContext} -import amf.shapes.internal.spec.common.parser.{IgnoreAnnotationSchemaValidatorBuilder, IgnoreCriteria, SpecSettings} -import org.yaml.model.{YMap, YMapEntry, YNode} +import amf.shapes.internal.spec.common.parser.{IgnoreCriteria, SpecSettings} +import org.yaml.model.{YMap, YMapEntry} import scala.collection.mutable import scala.language.postfixOps @@ -87,7 +86,3 @@ abstract class OasLikeWebApiContext( override def autoGeneratedAnnotation(s: Shape): Unit = ParsingHelpers.oasAutoGeneratedAnnotation(s) } - - - - diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasEndpointParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasEndpointParser.scala index 9c0c3f4566..65f88801ea 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasEndpointParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasEndpointParser.scala @@ -106,11 +106,7 @@ abstract class OasEndpointParser(entry: YMapEntry, parentId: String, collector: map.regex( operationsRegex, entries => { - val operations = mutable.ListBuffer[Operation]() - entries.foreach { entry => - val operationParser = ctx.factory.operationParser(entry, (o: Operation) => o) - operations += operationParser.parse() - } + val operations = parseOperations(entries) endpoint.setWithoutId( EndPointModel.Operations, AmfArray(operations, Annotations.inferred()), diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasLikeEndpointParser.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasLikeEndpointParser.scala index 20742eea42..333d05c029 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasLikeEndpointParser.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/spec/oas/parser/domain/OasLikeEndpointParser.scala @@ -1,6 +1,6 @@ package amf.apicontract.internal.spec.oas.parser.domain -import amf.apicontract.client.scala.model.domain.EndPoint +import amf.apicontract.client.scala.model.domain.{EndPoint, Operation} import amf.apicontract.internal.metamodel.domain.EndPointModel import amf.apicontract.internal.spec.common.parser._ import amf.apicontract.internal.spec.oas.parser.context.{OasLikeWebApiContext, RemoteNodeNavigation} @@ -11,6 +11,8 @@ import amf.core.internal.utils.{AmfStrings, TemplateUri} import amf.shapes.internal.spec.common.parser.AnnotationParser import org.yaml.model._ +import scala.collection.mutable + abstract class OasLikeEndpointParser(entry: YMapEntry, parentId: String, collector: List[EndPoint])(implicit val ctx: OasLikeWebApiContext ) extends SpecParserOps { @@ -86,4 +88,13 @@ abstract class OasLikeEndpointParser(entry: YMapEntry, parentId: String, collect endpoint } + + protected def parseOperations(entries: Iterable[YMapEntry]): Seq[Operation] = { + val operations = mutable.ListBuffer[Operation]() + entries.foreach { entry => + val operationParser = ctx.factory.operationParser(entry, (o: Operation) => o) + operations += operationParser.parse() + } + operations + } } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20EditingPipeline.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20EditingPipeline.scala index 61c8c41261..f17ebcf187 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20EditingPipeline.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20EditingPipeline.scala @@ -11,6 +11,7 @@ import amf.apicontract.internal.spec.common.transformation.stage.{ AnnotationRemovalStage, ChannelServersResolutionStage, OpenApiParametersNormalizationStage, + OperationsSecurityResolutionStage, ParametersNormalizationStage, PathDescriptionNormalizationStage } @@ -18,7 +19,6 @@ import amf.apicontract.internal.transformation.stages.WebApiReferenceResolutionS import amf.core.client.common.transform._ import amf.core.client.common.validation.{Async20Profile, ProfileName} import amf.core.client.scala.transform.TransformationStep -import amf.core.internal.remote.AsyncApi20 import amf.core.internal.transform.stages.SourceInformationStage import amf.shapes.internal.domain.resolution.ShapeNormalizationForUnitStage @@ -42,7 +42,8 @@ class Async20EditingPipeline private (urlShortening: Boolean = true, override va new PathDescriptionNormalizationStage(profileName, keepEditingInfo = true), new AnnotationRemovalStage(), new SemanticExtensionFlatteningStage, - new ChannelServersResolutionStage(Async20Profile) + new ChannelServersResolutionStage(Async20Profile), + new OperationsSecurityResolutionStage(Async20Profile) ) ++ url :+ SourceInformationStage } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20TransformationPipeline.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20TransformationPipeline.scala index 16304258eb..1a8c966a89 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20TransformationPipeline.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/Async20TransformationPipeline.scala @@ -10,6 +10,7 @@ import amf.apicontract.internal.spec.async.transformation.{ import amf.apicontract.internal.spec.common.transformation.stage.{ AnnotationRemovalStage, ChannelServersResolutionStage, + OperationsSecurityResolutionStage, PathDescriptionNormalizationStage } import amf.apicontract.internal.transformation.stages.WebApiReferenceResolutionStage @@ -42,7 +43,8 @@ class Async20TransformationPipeline private (override val name: String) extends new AnnotationRemovalStage(), new SemanticExtensionFlatteningStage, SourceInformationStage, - new ChannelServersResolutionStage(Async20Profile) + new ChannelServersResolutionStage(Async20Profile), + new OperationsSecurityResolutionStage(Async20Profile) ) } diff --git a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/ValidationTransformationPipeline.scala b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/ValidationTransformationPipeline.scala index 98af3bb786..514bd703da 100644 --- a/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/ValidationTransformationPipeline.scala +++ b/amf-api-contract/shared/src/main/scala/amf/apicontract/internal/transformation/ValidationTransformationPipeline.scala @@ -7,6 +7,7 @@ import amf.apicontract.internal.spec.common.transformation.stage.{ ChannelServersResolutionStage, MediaTypeResolutionStage, OpenApiParametersNormalizationStage, + OperationsSecurityResolutionStage, ParametersNormalizationStage, PayloadAndParameterResolutionStage, Raml10ParametersNormalizationStage, @@ -48,7 +49,8 @@ class ValidationTransformationPipeline private[amf] ( new SemanticExtensionFlatteningStage, SourceInformationStage, new AnnotationRemovalStage(), - new ChannelServersResolutionStage(profile) + new ChannelServersResolutionStage(profile), + new OperationsSecurityResolutionStage(profile) ) private def parameterNormalizationStageFor(profile: ProfileName): ParametersNormalizationStage = { diff --git a/amf-cli/shared/src/test/resources/resolution/async20/operation-security-explicit.yaml b/amf-cli/shared/src/test/resources/resolution/async20/operation-security-explicit.yaml new file mode 100644 index 0000000000..8c34cd5299 --- /dev/null +++ b/amf-cli/shared/src/test/resources/resolution/async20/operation-security-explicit.yaml @@ -0,0 +1,32 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: mykafkacluster.org:8092 + protocol: kafka-secure + security: + - + oauth2: + - write:pets + - read:pets +channels: + some/events: + subscribe: + security: + - + oauth2: + - read:pets diff --git a/amf-cli/shared/src/test/resources/resolution/async20/operation-security-implicit.yaml b/amf-cli/shared/src/test/resources/resolution/async20/operation-security-implicit.yaml new file mode 100644 index 0000000000..eccffb11a1 --- /dev/null +++ b/amf-cli/shared/src/test/resources/resolution/async20/operation-security-implicit.yaml @@ -0,0 +1,28 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: mykafkacluster.org:8092 + protocol: kafka-secure + security: + - + oauth2: + - write:pets + - read:pets +channels: + some/events: + subscribe: {} diff --git a/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-explicit.yaml b/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-explicit.yaml new file mode 100644 index 0000000000..50808810f8 --- /dev/null +++ b/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-explicit.yaml @@ -0,0 +1,34 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: mykafkacluster.org:8092 + protocol: kafka-secure + security: + - + oauth2: + - write:pets + - read:pets +channels: + some/events: + servers: + - production + subscribe: + security: + - + oauth2: + - read:pets \ No newline at end of file diff --git a/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-implicit.yaml b/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-implicit.yaml new file mode 100644 index 0000000000..fe1e3b4c49 --- /dev/null +++ b/amf-cli/shared/src/test/resources/upanddown/cycle/async20/operation-security-implicit.yaml @@ -0,0 +1,31 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: mykafkacluster.org:8092 + protocol: kafka-secure + security: + - + oauth2: + - write:pets + - read:pets +channels: + some/events: + servers: + - production + subscribe: + security: [] \ No newline at end of file diff --git a/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-explicit.yaml b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-explicit.yaml new file mode 100644 index 0000000000..d4b12263a7 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-explicit.yaml @@ -0,0 +1,33 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: "mykafkacluster.org:8092" + protocol: kafka-secure + security: + - oauth2: + - write:pets + - read:pets +channels: + some/events: + servers: + - production + subscribe: + # Note that an operation level security must still satisfy security requirements specified at the server level. + security: + - oauth2: + - read:pets diff --git a/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-implicit.yaml b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-implicit.yaml new file mode 100644 index 0000000000..ea3a28ba85 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security-implicit.yaml @@ -0,0 +1,28 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: "mykafkacluster.org:8092" + protocol: kafka-secure + security: + - oauth2: + - write:pets + - read:pets +channels: + some/events: + subscribe: + security: [ ] diff --git a/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml new file mode 100644 index 0000000000..c7158a79db --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml @@ -0,0 +1,34 @@ +asyncapi: 2.4.0 +info: + title: API + version: "1.0" +components: + securitySchemes: + oauth2: + type: oauth2 + description: oauth2 security scheme + flows: + implicit: + authorizationUrl: https://a.ml/ + refreshUrl: https://a.ml/ + scopes: + write:pets: modify pets in your account + read:pets: read your pets +servers: + production: + url: "mykafkacluster.org:8092" + protocol: kafka-secure + security: + - oauth2: + - write:pets + - read:pets +channels: + some/events: + servers: + - production + subscribe: + # Note that an operation level security must still satisfy security requirements specified at the server level. + security: + - oauth2: + - read:pets + - this should throw not found error \ No newline at end of file diff --git a/amf-cli/shared/src/test/resources/validations/reports/async20/operation-security.report b/amf-cli/shared/src/test/resources/validations/reports/async20/operation-security.report new file mode 100644 index 0000000000..0dd313e107 --- /dev/null +++ b/amf-cli/shared/src/test/resources/validations/reports/async20/operation-security.report @@ -0,0 +1,14 @@ +ModelId: file://amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml +Profile: +Conforms: false +Number of results: 1 + +Level: Violation + +- Constraint: http://a.ml/vocabularies/amf/parser#unknown-scope + Message: Scope 'this should throw not found error' not found in settings of declared secured by oauth2. + Severity: Violation + Target: file://amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml#/async-api/endpoint/some%2Fevents/supportedOperation/subscribe/security/requirement_1/schemes/oauth2/settings/oauth2/flows/default-flow/scope/this%20should%20throw%20not%20found%20error + Property: ValueType(Namespace(http://a.ml/vocabularies/security#),scope) + Range: [(34,14)-(34,47)] + Location: file://amf-cli/shared/src/test/resources/validations/async20/validations/operation-security.yaml diff --git a/amf-cli/shared/src/test/scala/amf/emit/Async20CycleTest.scala b/amf-cli/shared/src/test/scala/amf/emit/Async20CycleTest.scala index 746c9877f3..c12e7ddcc8 100644 --- a/amf-cli/shared/src/test/scala/amf/emit/Async20CycleTest.scala +++ b/amf-cli/shared/src/test/scala/amf/emit/Async20CycleTest.scala @@ -173,6 +173,16 @@ class Async20CycleTest extends FunSuiteCycleTests { "pulsar binding only required keys", "bindings/pulsar-binding-only-required.yaml", "bindings/pulsar-binding-only-required.yaml" + ), + FixtureData( + "async 2.4+ explicit security field in operation bindings", + "operation-security-explicit.yaml", + "operation-security-explicit.yaml" + ), + FixtureData( + "async 2.4+ implicit security field in operation bindings", + "operation-security-implicit.yaml", + "operation-security-implicit.yaml" ) // TODO: figure out why this test is commented out diff --git a/amf-cli/shared/src/test/scala/amf/resolution/Async20ResolutionTest.scala b/amf-cli/shared/src/test/scala/amf/resolution/Async20ResolutionTest.scala index 78ade6a51a..ad43c527b8 100644 --- a/amf-cli/shared/src/test/scala/amf/resolution/Async20ResolutionTest.scala +++ b/amf-cli/shared/src/test/scala/amf/resolution/Async20ResolutionTest.scala @@ -253,6 +253,26 @@ class Async20ResolutionTest extends ResolutionTest { ) } + // W-12689962 + test("async should not emit operation security if not specified") { + cycle( + "operation-security-implicit.yaml", + "operation-security-implicit.yaml", + Async20YamlHint, + target = Async20YamlHint + ) + } + + // W-12689962 + test("async should emit operation security keyword") { + cycle( + "operation-security-explicit.yaml", + "operation-security-explicit.yaml", + Async20YamlHint, + target = Async20YamlHint + ) + } + override def transform(unit: BaseUnit, config: CycleConfig, amfConfig: AMFConfiguration): BaseUnit = { super.transform(unit, config, AsyncAPIConfiguration.Async20()) } diff --git a/amf-cli/shared/src/test/scala/amf/resolution/merge/JsonMergePatchTest.scala b/amf-cli/shared/src/test/scala/amf/resolution/merge/JsonMergePatchTest.scala index ccc0b7f582..4d0f9b0376 100644 --- a/amf-cli/shared/src/test/scala/amf/resolution/merge/JsonMergePatchTest.scala +++ b/amf-cli/shared/src/test/scala/amf/resolution/merge/JsonMergePatchTest.scala @@ -5,7 +5,7 @@ import amf.apicontract.client.scala.model.document.APIContractProcessingData import amf.apicontract.client.scala.model.domain.{Message, Operation} import amf.apicontract.internal.spec.async.Subscribe import amf.apicontract.internal.spec.async.parser.context.{Async2WebApiContext, AsyncWebApiContext} -import amf.apicontract.internal.spec.async.parser.domain.{Async20MessageParser, AsyncOperationParser} +import amf.apicontract.internal.spec.async.parser.domain.{Async20MessageParser, Async20OperationParser} import amf.apicontract.internal.spec.async.transformation.AsyncJsonMergePatch import amf.apicontract.internal.spec.common.transformation.stage.{AsyncKeyCriteria, JsonMergePatch} import amf.core.client.scala.adoption.IdAdopter @@ -175,7 +175,7 @@ class JsonMergePatchTest extends MultiJsonldAsyncFunSuite with Matchers with Fil document .as[YMap] .key("subscribe") - .map(entry => AsyncOperationParser(entry, (o: Operation) => o.withId(id))(getBogusParserCtx).parse()) + .map(entry => Async20OperationParser(entry, (o: Operation) => o.withId(id))(getBogusParserCtx).parse()) .get } } diff --git a/amf-cli/shared/src/test/scala/amf/validation/AMFModelAssertionTest.scala b/amf-cli/shared/src/test/scala/amf/validation/AMFModelAssertionTest.scala index 0b50e932e6..589bf78f71 100644 --- a/amf-cli/shared/src/test/scala/amf/validation/AMFModelAssertionTest.scala +++ b/amf-cli/shared/src/test/scala/amf/validation/AMFModelAssertionTest.scala @@ -2,6 +2,7 @@ package amf.validation import amf.apicontract.client.scala._ import amf.apicontract.client.scala.model.domain.api.WebApi +import amf.apicontract.client.scala.model.domain.security.OAuth2Settings import amf.apicontract.internal.metamodel.domain.{EndPointModel, OperationModel} import amf.apicontract.internal.spec.async.NotFinishedAsync20ParsePlugin import amf.core.client.common.transform.PipelineId @@ -599,7 +600,7 @@ class AMFModelAssertionTest extends AsyncFunSuite with Matchers { } // W-12689955 - test("async add channel servers transformation") { + test("async 2.2+ add channel servers transformation") { val api = s"$basePath/async20/validations/channel-servers.yaml" asyncClient.parse(api) flatMap { parseResult => val transformResult = asyncClient.transform(parseResult.baseUnit) @@ -628,4 +629,42 @@ class AMFModelAssertionTest extends AsyncFunSuite with Matchers { serversInRender.length shouldBe 1 } } + + // W-12689962 + test("async2.4+ explicit operation security facet") { + val api = s"$basePath/async20/validations/operation-security-explicit.yaml" + asyncClient.parse(api) flatMap { parseResult => + val transformResult = asyncClient.transform(parseResult.baseUnit) + val transformBU = transformResult.baseUnit + val endpoint = getFirstEndpoint(transformBU, isWebApi = false) + + val serverSecurity = endpoint.servers.head.security.head.schemes.head + val operationSecurity = endpoint.operations.head.security.head.schemes.head + + val serverSecurityScopes = serverSecurity.settings.asInstanceOf[OAuth2Settings].flows.head.scopes + val operationSecurityScopes = operationSecurity.settings.asInstanceOf[OAuth2Settings].flows.head.scopes + + serverSecurityScopes.size shouldBe 2 + operationSecurityScopes.size shouldBe 1 + } + } + + // W-12689962 + test("async2.4+ resolve implicit operation security facet") { + val api = s"$basePath/async20/validations/operation-security-implicit.yaml" + asyncClient.parse(api) flatMap { parseResult => + val transformResult = asyncClient.transform(parseResult.baseUnit) + val transformBU = transformResult.baseUnit + val endpoint = getFirstEndpoint(transformBU, isWebApi = false) + + val serverSecurity = endpoint.servers.head.security.head.schemes.head + val operationSecurity = endpoint.operations.head.security.head.schemes.head + + val serverSecurityScopes = serverSecurity.settings.asInstanceOf[OAuth2Settings].flows.head.scopes + val operationSecurityScopes = operationSecurity.settings.asInstanceOf[OAuth2Settings].flows.head.scopes + + serverSecurityScopes.size shouldBe 2 + operationSecurityScopes.size shouldBe 2 + } + } } diff --git a/amf-cli/shared/src/test/scala/amf/validation/Async20UniquePlatformUnitValidationsTest.scala b/amf-cli/shared/src/test/scala/amf/validation/Async20UniquePlatformUnitValidationsTest.scala index f3131862e4..f555581f16 100644 --- a/amf-cli/shared/src/test/scala/amf/validation/Async20UniquePlatformUnitValidationsTest.scala +++ b/amf-cli/shared/src/test/scala/amf/validation/Async20UniquePlatformUnitValidationsTest.scala @@ -357,6 +357,10 @@ class Async20UniquePlatformUnitValidationsTest extends UniquePlatformReportGenTe validate("channel-servers.yaml", Some("channel-servers.report")) } + test("Async 2.4+ operation security property with undefined security scheme") { + validate("operation-security.yaml", Some("operation-security.report")) + } + test("Async 2.2+ AnypointMQ Closed Shape validation") { validate("anypoint-binding-extra-key.yaml", Some("anypoint-binding-extra-key.report")) }