-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#46: initial implementation of auth inputs
- Loading branch information
Showing
20 changed files
with
338 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package tapir | ||
import java.util.Base64 | ||
|
||
import tapir.Codec.PlainCodec | ||
import tapir.model.UsernamePassword | ||
|
||
object TapirAuth { | ||
private val BasicAuthType = "Basic" | ||
private val BearerAuthType = "Bearer" | ||
|
||
def apiKey[T](input: EndpointInput.Single[T]): EndpointInput.Auth.ApiKey[T] = EndpointInput.Auth.ApiKey[T](input) | ||
val basic: EndpointInput.Auth.Http[UsernamePassword] = httpAuth(BasicAuthType, usernamePasswordCodec(credentialsCodec(BasicAuthType))) | ||
val bearer: EndpointInput.Auth.Http[String] = httpAuth(BearerAuthType, credentialsCodec(BearerAuthType)) | ||
|
||
private def httpAuth[T](authType: String, codec: PlainCodec[T]): EndpointInput.Auth.Http[T] = | ||
EndpointInput.Auth.Http(authType, header[T]("Authorization")(CodecForMany.fromCodec(codec))) | ||
|
||
private def usernamePasswordCodec(baseCodec: PlainCodec[String]): PlainCodec[UsernamePassword] = { | ||
def decode(s: String): DecodeResult[UsernamePassword] = | ||
try { | ||
val s2 = new String(Base64.getDecoder.decode(s)) | ||
val up = s2.split(":", 2) match { | ||
case Array() => UsernamePassword("", None) | ||
case Array(u) => UsernamePassword(u, None) | ||
case Array(u, "") => UsernamePassword(u, None) | ||
case Array(u, p) => UsernamePassword(u, Some(p)) | ||
} | ||
DecodeResult.Value(up) | ||
} catch { | ||
case e: Exception => DecodeResult.Error(s, e) | ||
} | ||
|
||
def encode(up: UsernamePassword): String = | ||
Base64.getEncoder.encodeToString(s"${up.username}:${up.password.getOrElse("")}".getBytes("UTF-8")) | ||
|
||
baseCodec.mapDecode(decode)(encode) | ||
} | ||
|
||
private def credentialsCodec(authType: String): PlainCodec[String] = { | ||
val authTypeWithSpace = authType + " " | ||
val prefixLength = authTypeWithSpace.length | ||
def removeAuthType(v: String): DecodeResult[String] = | ||
if (v.startsWith(authType)) DecodeResult.Value(v.substring(prefixLength)) | ||
else DecodeResult.Error(v, new IllegalArgumentException(s"The given value doesn't start with $authType")) | ||
Codec.stringPlainCodecUtf8.mapDecode(removeAuthType)(v => s"$authType $v") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package tapir.model | ||
|
||
case class UsernamePassword(username: String, password: Option[String]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
docs/openapi-docs/src/main/scala/tapir/docs/openapi/SecuritySchemesForEndpoints.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package tapir.docs.openapi | ||
import tapir.openapi.SecurityScheme | ||
import tapir.{Endpoint, EndpointIO, EndpointInput} | ||
|
||
import scala.annotation.tailrec | ||
|
||
private[openapi] object SecuritySchemesForEndpoints { | ||
|
||
def apply(es: Iterable[Endpoint[_, _, _, _]]): SecuritySchemes = { | ||
val auths = es.flatMap(e => e.input.auths) | ||
val authSecuritySchemes = auths.map(a => (a, authToSecurityScheme(a))) | ||
val securitySchemes = authSecuritySchemes.map(_._2).toSet | ||
val namedSecuritySchemes = nameSecuritySchemes(securitySchemes.toVector, Set(), Map()) | ||
|
||
authSecuritySchemes.map { case (a, s) => a -> ((namedSecuritySchemes(s), s)) }.toMap | ||
} | ||
|
||
@tailrec | ||
private def nameSecuritySchemes(schemes: Vector[SecurityScheme], | ||
takenNames: Set[SchemeName], | ||
acc: Map[SecurityScheme, SchemeName]): Map[SecurityScheme, SchemeName] = { | ||
schemes match { | ||
case Vector() => acc | ||
case scheme +: tail => | ||
val baseName = scheme.`type` + "Auth" | ||
val name = uniqueName(baseName, !takenNames.contains(_)) | ||
nameSecuritySchemes(tail, takenNames + name, acc + (scheme -> name)) | ||
} | ||
} | ||
|
||
private def authToSecurityScheme(a: EndpointInput.Auth[_]): SecurityScheme = a match { | ||
case EndpointInput.Auth.ApiKey(input) => | ||
val (name, in) = apiKeyInputNameAndIn(input.asVectorOfBasic()) | ||
SecurityScheme("apiKey", None, Some(name), Some(in), None, None, None, None) | ||
case EndpointInput.Auth.Http(scheme, _) => | ||
SecurityScheme("http", None, None, None, Some(scheme.toLowerCase()), None, None, None) | ||
} | ||
|
||
private def apiKeyInputNameAndIn(input: Vector[EndpointInput.Basic[_]]) = input match { | ||
case Vector(EndpointIO.Header(name, _, _)) => (name, "header") | ||
case Vector(EndpointInput.Query(name, _, _)) => (name, "query") | ||
// TODO cookie | ||
case _ => throw new IllegalArgumentException(s"Api key authentication can only be read from headers, queries or cookies, not: $input") | ||
} | ||
} |
18 changes: 17 additions & 1 deletion
18
docs/openapi-docs/src/main/scala/tapir/docs/openapi/package.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,19 @@ | ||
package tapir.docs | ||
|
||
package object openapi extends OpenAPIDocs | ||
import tapir.EndpointInput | ||
import tapir.openapi.SecurityScheme | ||
|
||
package object openapi extends OpenAPIDocs { | ||
private[openapi] type SchemeName = String | ||
private[openapi] type SecuritySchemes = Map[EndpointInput.Auth[_], (SchemeName, SecurityScheme)] | ||
|
||
private[openapi] def uniqueName(base: String, isUnique: String => Boolean): String = { | ||
var i = 0 | ||
var result = base | ||
while (!isUnique(result)) { | ||
i += 1 | ||
result = base + i | ||
} | ||
result | ||
} | ||
} |
Oops, something went wrong.