Skip to content

Commit

Permalink
Try to Either migration. #14
Browse files Browse the repository at this point in the history
  • Loading branch information
bblfish committed Nov 29, 2022
1 parent 32f153d commit f0205be
Show file tree
Hide file tree
Showing 23 changed files with 355 additions and 318 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ import cats.parse.Parser
import run.cosy.akka.http.AkkaTp
import run.cosy.akka.http.headers.Encoding.UnicodeString
import run.cosy.akka.http.headers.{BetterCustomHeader, BetterCustomHeaderCompanion}
import run.cosy.http.auth.{HTTPHeaderParseException, InvalidSigException, SignatureInputMatcher}
import run.cosy.http.auth.{
HTTPHeaderParseException,
InvalidSigException,
ParsingExc,
SignatureInputMatcher
}
import run.cosy.http.headers.*
import run.cosy.http.headers.Rfc8941.{
IList,
Expand Down Expand Up @@ -89,12 +94,12 @@ object `Signature-Input`
parse(h.value).toOption
case _ => None

def parse(value: String): Try[SigInputs] =
def parse(value: String): Either[ParsingExc, SigInputs] =
Rfc8941.Parser.sfDictionary.parseAll(value) match
case Left(e) => Failure(HTTPHeaderParseException(e, value))
case Left(e) => Left(HTTPHeaderParseException(e, value))
case Right(lm) => SigInputs(lm).toRight {
InvalidSigException(
"Signature-Input Header Parses but data structure is not appropriate"
)
}.toTry
}
end `Signature-Input`
13 changes: 9 additions & 4 deletions akka/src/main/scala/run/cosy/akka/http/headers/Signature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import akka.http.scaladsl.model.{HttpHeader, ParsingException}
import run.cosy.akka.http.AkkaTp
import run.cosy.akka.http.headers.{BetterCustomHeader, BetterCustomHeaderCompanion, Signature}
import run.cosy.http.Http.Header
import run.cosy.http.auth.{HTTPHeaderParseException, InvalidSigException, SignatureMatcher}
import run.cosy.http.auth.{
HTTPHeaderParseException,
InvalidSigException,
ParsingExc,
SignatureMatcher
}
import run.cosy.http.headers
import run.cosy.http.headers.Rfc8941.{Bytes, IList, PItem, SfDict}
import run.cosy.http.headers.{Rfc8941, Signatures}
Expand Down Expand Up @@ -55,10 +60,10 @@ object Signature
case _: (RawHeader | CustomHeader) if h.lowercaseName == lowercaseName =>
parse(h.value).toOption
case _ => None
def parse(value: String): Try[Signatures] =
def parse(value: String): Either[ParsingExc, Signatures] =
Rfc8941.Parser.sfDictionary.parseAll(value) match
case Left(e) => Failure(HTTPHeaderParseException(e, value))
case Left(e) => Left(HTTPHeaderParseException(e, value))
case Right(lm) => Signatures(lm).toRight(
InvalidSigException("Signature Header Parses but data structure is not appropriate")
).toTry
)
end Signature
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,54 @@

package run.cosy.akka.http.messages

import akka.http.scaladsl.model.{HttpMessage, HttpRequest, HttpResponse}
import akka.http.scaladsl.model.*
import akka.http.scaladsl.model.headers.Host
import akka.http.scaladsl.settings.ParserSettings.ConflictingContentTypeHeaderProcessingMode.NoContentType
import cats.Id
import cats.data.NonEmptyList
import run.cosy.akka.http.AkkaTp
import run.cosy.akka.http.AkkaTp.HT
import run.cosy.http.Http
import run.cosy.http.Http.{Request, Response}
import run.cosy.http.auth.{AttributeException, HTTPHeaderParseException, SelectorException}
import run.cosy.http.auth.{
AttributeException,
HTTPHeaderParseException,
ParsingExc,
SelectorException
}
import run.cosy.http.headers.Rfc8941.{Param, Params}
import run.cosy.http.headers.{ParsingException, Rfc8941}
import run.cosy.http.messages.*

import java.nio.charset.StandardCharsets
import scala.collection.immutable.ListMap
import scala.util.{Failure, Success, Try}
import akka.http.scaladsl.model.headers.Host
import akka.http.scaladsl.model.HttpHeader
import akka.http.scaladsl.settings.ParserSettings.ConflictingContentTypeHeaderProcessingMode.NoContentType

class SelectorFnsAkka(using sc: ServerContext)
extends SelectorFns[Id, HT]:

override val method: RequestFn =
RequestAkka { req => Success(req.method.value) }
RequestAkka { req => Right(req.method.value) }

override val authority: RequestFn =
RequestAkka { req =>
Try(
req.effectiveUri(sc.secure, sc.defaultHost.map(Host(_)).getOrElse(Host.empty))
.authority.toString().toLowerCase(java.util.Locale.ROOT).nn
)
try
Right(req.effectiveUri(
sc.secure,
sc.defaultHost.map(Host(_))
.getOrElse(Host.empty)
).authority.toString().toLowerCase(java.util.Locale.ROOT).nn)
catch
case urlEx: IllegalUriException =>
Left(
SelectorException("cannot calculate effectuve url for request. " + urlEx.getMessage)
)
}

/** best not used if not HTTP1.1 according to spec. Does not even work with Akka (see test suite)
*/
override val requestTarget: RequestFn =
RequestAkka { req => Success(req.uri.toString()) }
RequestAkka { req => Right(req.uri.toString()) }

// raw headers, no interpretation
override def requestHeaders(headerName: HeaderId): RequestFn =
Expand All @@ -68,12 +79,12 @@ class SelectorFnsAkka(using sc: ServerContext)

override val path: RequestFn =
RequestAkka { req =>
Success(req.uri.path.toString())
Right(req.uri.path.toString())
}

override val query: RequestFn =
RequestAkka { req =>
Try(
Right(
req.uri.queryString(StandardCharsets.US_ASCII.nn)
.map("?" + _).getOrElse("?")
)
Expand All @@ -82,59 +93,61 @@ class SelectorFnsAkka(using sc: ServerContext)
override def queryParam(name: Rfc8941.SfString): RequestFn =
RequestAkka { req =>
req.uri.query().getAll(name.asciiStr).reverse match
case Nil => Failure(SelectorException(
case Nil => Left(SelectorException(
s"No query parameter with key ${name} found. Suspicious."
))
case head :: tail => Success(NonEmptyList(head, tail))
case head :: tail => Right(NonEmptyList(head, tail))
}

override val scheme: RequestFn =
RequestAkka { req =>
Try(req.effectiveUri(
Right(req.effectiveUri(
securedConnection = sc.secure,
defaultHostHeader = sc.defaultHost.map(Host(_)).getOrElse(Host.empty)
).scheme)
}

override val targetUri: RequestFn = RequestAkka { req =>
Success(req.effectiveUri(
Right(req.effectiveUri(
securedConnection = sc.secure,
defaultHostHeader = sc.defaultHost.map(Host(_)).getOrElse(Host.empty)
).toString())
}

override val status: ResponseFn = ResponseAkka { resp =>
Success("" + resp.status.intValue)
Right("" + resp.status.intValue)
}

case class RequestAkka(
val sigValues: HttpRequest => Try[String | NonEmptyList[String]]
val sigValues: HttpRequest => Either[ParsingExc, String | NonEmptyList[String]]
) extends SelectorFn[Http.Request[Id, HT]]:
override val signingValues: Request[Id, HT] => Try[String | NonEmptyList[String]] =
override val signingValues
: Request[Id, HT] => Either[ParsingExc, String | NonEmptyList[String]] =
msg => sigValues(msg.asInstanceOf[HttpRequest])

case class ResponseAkka(
val sigValues: HttpResponse => Try[String | NonEmptyList[String]]
val sigValues: HttpResponse => Either[ParsingExc, String | NonEmptyList[String]]
) extends SelectorFn[Http.Response[Id, HT]]:
override val signingValues: Response[Id, HT] => Try[String | NonEmptyList[String]] =
override val signingValues
: Response[Id, HT] => Either[ParsingExc, String | NonEmptyList[String]] =
msg => sigValues(msg.asInstanceOf[HttpResponse])

end SelectorFnsAkka

object SelectorAkka:
import run.cosy.http.headers.Rfc8941.Serialise.given

def getHeaders(name: HeaderId)(msg: HttpMessage): Try[NonEmptyList[String]] =
def getHeaders(name: HeaderId)(msg: HttpMessage): Either[ParsingExc, NonEmptyList[String]] =
val N = name.specName
msg.headers.collect { case HttpHeader(N, value) => value.trim.nn }.toList match
case Nil =>
name.specName match
case "content-length" => msg.entity.contentLengthOption
.toRight(SelectorException("no content-length header set"))
.toTry.map(l => NonEmptyList.one("" + l))
.map(l => NonEmptyList.one("" + l))
case "content-type" if msg.entity.contentType != NoContentType =>
Success(NonEmptyList.one(msg.entity.contentType.value))
Right(NonEmptyList.one(msg.entity.contentType.value))
case _ =>
Failure(SelectorException(s"No headers named ${name.canon} selectable in request"))
case head :: tail => Success(NonEmptyList(head, tail))
Left(SelectorException(s"No headers named ${name.canon} selectable in request"))
case head :: tail => Right(NonEmptyList(head, tail))
end SelectorAkka
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import run.cosy.http.Http
given ServerContext = ServerContext("bblfish.net", true)
val xxxx = new run.cosy.akka.http.messages.SelectorFnsAkka(using ServerContext("bblfish.net", true))

class AkkaReqSigSuite extends RequestSigSuite[cats.Id, HT](
class AkkaReqSigSuite extends SigInputReqSuite[cats.Id, HT](
run.cosy.http.auth.AkkaHttpMessageSignature,
new run.cosy.http.messages.RequestSelectorDB(
new AtSelectors[cats.Id, HT](using xxxx) {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,33 @@ import run.cosy.http.messages.Parameters.nameTk
import run.cosy.http4s.Http4sTp.HT as H4
import run.cosy.platform
import run.cosy.http.headers.Rfc8941
import run.cosy.http.auth.{AttributeException, SelectorException}
import run.cosy.http.auth.{AttributeException, ParsingExc, SelectorException}
import run.cosy.http4s.messages.SelectorFnsH4.getHeaders

import scala.util.Try
import scala.util.{Failure, Success}
import org.typelevel.ci.CIString

class SelectorFnsH4[F[_]](using sc: ServerContext) extends SelectorFns[F, H4]:
import SelectorFnsH4 as SF
val SF = SelectorFnsH4

override def method: RequestFn =
RequestSelH4(req => Success(req.method.name))
RequestSelH4(req => Right(req.method.name))

override def authority: RequestFn = RequestSelH4(req =>
for
auth <- SF.authorityFor(req)
.toRight(SelectorException("could not construct authority for request"))
.toTry
yield platform.StringUtil.toLowerCaseInsensitive(auth.renderString)
)

/** best not used if not HTTP1.1 */
override def requestTarget: RequestFn = RequestSelH4(req => Success(req.uri.renderString))
override def requestTarget: RequestFn = RequestSelH4(req => Right(req.uri.renderString))

override def path: RequestFn = RequestSelH4(req => Success(req.uri.path.toString()))
override def path: RequestFn = RequestSelH4(req => Right(req.uri.path.toString()))

override def query: RequestFn = RequestSelH4(req =>
Success {
Right {
val q: Query = req.uri.query
if q == Query.empty then "?"
else if q == Query.blank then "?"
Expand All @@ -63,14 +62,12 @@ class SelectorFnsH4[F[_]](using sc: ServerContext) extends SelectorFns[F, H4]:
)

override def queryParam(name: Rfc8941.SfString): RequestFn = RequestSelH4(req =>
Try {
req.uri.query.multiParams.get(name.asciiStr) match
case None => throw SelectorException(
s"No query parameter with key ${name.asciiStr} found. Suspicious."
)
case Some(Nil) => ""
case Some(head :: tail) => NonEmptyList(head, tail)
}
req.uri.query.multiParams.get(name.asciiStr) match
case None => Left(SelectorException(
s"No query parameter with key ${name.asciiStr} found. Suspicious."
))
case Some(Nil) => Right("")
case Some(head :: tail) => Right(NonEmptyList(head, tail))
)

override def scheme: RequestFn = RequestSelH4(req =>
Expand All @@ -85,7 +82,7 @@ class SelectorFnsH4[F[_]](using sc: ServerContext) extends SelectorFns[F, H4]:
)
)

override def status: ResponseFn = ResponseSelH4(req => Success("" + req.status.code))
override def status: ResponseFn = ResponseSelH4(req => Right("" + req.status.code))

override def requestHeaders(name: HeaderId): RequestFn = RequestSelH4(req =>
SF.getHeaders(req, name)
Expand All @@ -96,15 +93,17 @@ class SelectorFnsH4[F[_]](using sc: ServerContext) extends SelectorFns[F, H4]:
)

case class RequestSelH4(
val sigValues: H4Request[F] => Try[String | NonEmptyList[String]]
val sigValues: H4Request[F] => Either[ParsingExc, String | NonEmptyList[String]]
) extends SelectorFn[Http.Request[F, H4]]:
override val signingValues: Http.Request[F, H4] => Try[String | NonEmptyList[String]] =
override val signingValues
: Http.Request[F, H4] => Either[ParsingExc, String | NonEmptyList[String]] =
msg => sigValues(msg.asInstanceOf[H4Request[F]])

case class ResponseSelH4(
sigValues: H4Response[F] => Try[String | NonEmptyList[String]]
sigValues: H4Response[F] => Either[ParsingExc, String | NonEmptyList[String]]
) extends SelectorFn[Http.Response[F, H4]]:
override val signingValues: Http.Response[F, H4] => Try[String | NonEmptyList[String]] =
override val signingValues
: Http.Response[F, H4] => Either[ParsingExc, String | NonEmptyList[String]] =
msg => sigValues(msg.asInstanceOf[H4Response[F]])

end SelectorFnsH4
Expand All @@ -113,8 +112,8 @@ object SelectorFnsH4:

def getHeaders[F[_]](msg: H4Message[F], name: HeaderId) =
msg.headers.get(CIString(name.specName)).map(_.map(_.value)) match
case None => Failure(SelectorException(s"no header in request named $name"))
case Some(nel) => Success(nel)
case None => Left(SelectorException(s"no header in request named $name"))
case Some(nel) => Right(nel)

def defaultAuthorityOpt(scheme: Option[Uri.Scheme])(
using sc: ServerContext
Expand Down Expand Up @@ -149,16 +148,18 @@ object SelectorFnsH4:
def defaultScheme(using sc: ServerContext): Uri.Scheme =
if sc.secure then Uri.Scheme.https else Uri.Scheme.http

def effectiveUriFor[F[_]](req: H4Request[F])(using sc: ServerContext): Try[Uri] =
def effectiveUriFor[F[_]](req: H4Request[F])(using
sc: ServerContext
): Either[SelectorException, Uri] =
val uri = req.uri
if uri.scheme.isDefined && uri.authority.isDefined
then Success(uri)
then Right(uri)
else
val newUri = uri.copy(
scheme = uri.scheme.orElse(Some(defaultScheme)),
authority = authorityFor(req)
)
if newUri.authority.isDefined
then Success(newUri)
else Failure(SelectorException(s"cannot create effective Uri for req. Got: <$newUri>"))
then Right(newUri)
else Left(SelectorException(s"cannot create effective Uri for req. Got: <$newUri>"))
end effectiveUriFor
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@ package run.cosy.http.auth

import run.cosy.http.headers.RFC8941Exception

import java.nio.charset.CharacterCodingException

//used to be ResponseSummary(on: Uri, code: StatusCode, header: Seq[HttpHeader], respTp: ContentType)
//but it would be complicated to adapt for akka and http4s types
case class ResponseSummary(onUri: String, code: String, header: Seq[String], respTp: String)

class AuthExc(msg: String) extends Throwable(msg, null, true, false)
sealed class AuthExc(msg: String) extends Throwable(msg, null, true, false)
case class CryptoException(msg: String) extends AuthExc(msg)
case class AuthException(response: ResponseSummary, msg: String) extends AuthExc(msg)
case class InvalidCreatedFieldException(msg: String) extends AuthExc(msg)
case class InvalidExpiresFieldException(msg: String) extends AuthExc(msg)
case class UnableToCreateSigHeaderException(msg: String) extends AuthExc(msg)
case class SelectorException(msg: String) extends AuthExc(msg)
case class InvalidSigException(msg: String) extends AuthExc(msg)
case class KeyIdException(msg: String) extends AuthExc(msg)
case class AttributeException(msg: String) extends AuthExc(msg)

case class KeyIdException(msg: String) extends AuthExc(msg)

sealed class ParsingExc(msg: String) extends AuthExc(msg)
case class InvalidSigException(msg: String) extends ParsingExc(msg)
case class SelectorException(msg: String) extends ParsingExc(msg)
case class AttributeException(msg: String) extends ParsingExc(msg)
case class CharacterCodingExc(msg: String) extends ParsingExc(msg)
case class HTTPHeaderParseException(error: cats.parse.Parser.Error, httpHeader: String)
extends AuthExc(httpHeader)
extends ParsingExc(httpHeader)
Loading

0 comments on commit f0205be

Please sign in to comment.