All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.docs.apispec.SecurityRequirementsForEndpoints.scala Maven / Gradle / Ivy

The newest version!
package sttp.tapir.docs.apispec

import sttp.apispec.SecurityRequirement
import sttp.tapir.{AnyEndpoint, EndpointIO, EndpointInput}
import sttp.tapir.internal.{IterableToListMap, RichEndpoint, RichEndpointInput}

import scala.collection.immutable.{ListMap, ListSet}

private[docs] class SecurityRequirementsForEndpoints(securitySchemes: SecuritySchemes) {
  def apply(es: Iterable[AnyEndpoint]): List[SecurityRequirement] = ListSet(es.toList.flatMap(apply): _*).toList

  def apply(e: AnyEndpoint): List[SecurityRequirement] = {
    val auths = e.auths
    val nonEmptyAuths = auths.filterNot(_.isInputEmpty)
    // an emptyAuth is used as a marker that authentication is optional
    val hasEmptyAuth = auths.size != nonEmptyAuths.size

    // * auths in a single group always form a single security requirement
    // * multiple groups form alternate security requirements
    // * optional auths without a security requirement become alternate requirements
    val requirements: List[SecurityRequirement] = nonEmptyAuths.groupBy(_.info.group).toList.sortBy(_._1.getOrElse("")).flatMap {
      case (None, noGroupAuths) =>
        if (authOptional(noGroupAuths)) {
          // all optional, creating separate security requirements
          noGroupAuths.map(a => securityRequirement(List(a))).toList
        } else List(securityRequirement(noGroupAuths))
      case (Some(_), groupAuths) => List(securityRequirement(groupAuths))
    }

    // auth is also optional if there's a single requirement, where all the inputs are optional
    val securityOptional = hasEmptyAuth || (authOptional(auths) && requirements.size <= 1)

    if (requirements.isEmpty) List.empty
    else {
      if (securityOptional) {
        (ListMap.empty: SecurityRequirement) :: requirements
      } else {
        requirements
      }
    }
  }

  private def securityRequirement(auths: Seq[EndpointInput.Auth[_, _ <: EndpointInput.AuthType]]): SecurityRequirement = auths.flatMap {
    case auth @ EndpointInput.Auth(_, _, info: EndpointInput.AuthType.ScopedOAuth2, _) =>
      securitySchemes.get(auth).map(_._1).map((_, info.requiredScopes.toVector))
    case auth => securitySchemes.get(auth).map(_._1).map((_, Vector.empty))
  }.toListMap

  private def authOptional(auths: Seq[EndpointInput.Auth[_, _ <: EndpointInput.AuthType]]): Boolean =
    auths.flatMap(_.asVectorOfBasicInputs()).forall {
      case i: EndpointInput.Atom[_]          => i.codec.schema.isOptional
      case EndpointIO.OneOfBody(variants, _) => variants.forall(_.codec.schema.isOptional)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy