Skip to content

Commit

Permalink
Fix docstring example in AuthedContext and get rid of unsafe getAuth
Browse files Browse the repository at this point in the history
  • Loading branch information
chuwy committed Oct 7, 2018
1 parent acc4db3 commit 46d3787
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 27 deletions.
41 changes: 16 additions & 25 deletions core/src/main/scala/org/http4s/rho/AuthedContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,29 @@ package rho

import cats.Monad
import cats.data.{Kleisli, OptionT}
import org.http4s.rho.bits.{FailureResponseOps, SuccessResponse, TypedHeader}
import shapeless.{::, HNil}

import org.http4s.rho.bits.{FailureResponseOps, SuccessResponse, TypedHeader}


/** The [[AuthedContext]] provides a convenient way to define a RhoService
* which works with http4s authentication middleware.
* {{{
* case class User(name: String, id: UUID)
*
* object Auth {
* val authUser: Service[Request, User] = Kleisli({ _ =>
* Task.now(User("Test User", UUID.randomUUID()))
* })
*
* val authenticated = AuthMiddleware(authUser)
* val middleware = AuthMiddleware { req =>
* OptionT(IO(User("Bob", UUID.randomUUID())))
* }
*
* object MyAuth extends AuthedContext[User]
* object Auth extends AuthedContext[IO, User]
*
* object MyService extends RhoService {
* import MyAuth._
* GET +? param("foo", "bar") |>> { (req: Request, foo: String) =>
* val user = getAuth(req)
* if (user.name == "Test User") {
* Ok(s"just root with parameter 'foo=\$foo'")
* } else {
* BadRequest("This should not have happened.")
* }
* object BobService extends RhoService[IO] {
* GET +? param("foo", "bar") >>> Auth.auth |>> { (foo: String, user: User) =>
* Ok(s"Bob with id ${user.id}, foo $foo")
* }
* }
*
* val service = Auth.authenticated(MyAuth.toService(MyService.toService(SwaggerSupport())))
* val service = middleware.apply(Auth.toService(BobService.toRoutes()))
* }}}
*
* @tparam U authInfo type for this service.
Expand All @@ -44,7 +35,7 @@ class AuthedContext[F[_]: Monad, U] extends FailureResponseOps[F] {
/* Attribute key to lookup authInfo in request attributeMap . */
final private val authKey = AttributeKey[U]

/** Turn the [[HttpRoutes§]] into an `AuthedService`
/** Turn the [[HttpRoutes]] into an `AuthedService`
*
* @param routes [[HttpRoutes]] to convert
* @return An `AuthedService` which can be mounted by http4s servers.
Expand All @@ -57,13 +48,13 @@ class AuthedContext[F[_]: Monad, U] extends FailureResponseOps[F] {
}
}

/* Get the authInfo object from request. */
def getAuth(req: Request[F]): U = {
req.attributes.get[U](authKey).get
}
/** Get the authInfo object from request */
def getAuth(req: Request[F]): Option[U] =
req.attributes.get(authKey)

def auth(): TypedHeader[F, U :: HNil] = RhoDsl[F].genericRequestHeaderCapture[U] { req =>
req.attributes.get(authKey) match {
/** Request matcher to capture authentication information */
def auth: TypedHeader[F, U :: HNil] = RhoDsl[F].genericRequestHeaderCapture[U] { req =>
getAuth(req) match {
case Some(authInfo) => SuccessResponse(authInfo)
case None => error("Invalid auth configuration")
}
Expand Down
31 changes: 29 additions & 2 deletions core/src/test/scala/org/http4s/rho/AuthedContextSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ object MyAuth extends AuthedContext[IO, User]
object MyService extends RhoService[IO] {
import MyAuth._

GET +? param("foo", "bar") >>> auth |>> { (req: Request[IO], foo: String, user: User) =>
GET +? param("foo", "bar") >>> auth |>> { (_: Request[IO], foo: String, user: User) =>
if (user.name == "Test User") {
Ok(s"just root with parameter 'foo=$foo'")
} else {
BadRequest("This should not have happened.")
}
}

GET / "public" / 'place |>> { path: String => Ok(s"not authenticated at $path") }

GET / "private" / 'place |>> { (req: Request[IO], path: String) =>
getAuth(req) match {
case Some(user) => Ok(s"${user.name} at $path")
case None => Forbidden(s"not authenticated at $path")
}
}
}

class AuthedContextSpec extends Specification {
Expand All @@ -47,7 +56,25 @@ class AuthedContextSpec extends Specification {
if (resp.status == Status.Ok) {
val body = new String(resp.body.compile.toVector.unsafeRunSync().foldLeft(Array[Byte]())(_ :+ _))
body should_== "just root with parameter 'foo=bar'"
} else sys.error(s"Invalid response code: ${resp.status}")
} else ko(s"Invalid response code: ${resp.status}")
}

"Does not prevent route from being executed without authentication" in {
val request = Request[IO](Method.GET, Uri(path = "/public/public"))
val resp = routes.run(request).value.unsafeRunSync().getOrElse(Response.notFound)
if (resp.status == Status.Ok) {
val body = new String(resp.body.compile.toVector.unsafeRunSync().foldLeft(Array[Byte]())(_ :+ _))
body should_== "not authenticated at public"
} else ko(s"Invalid response code: ${resp.status}")
}

"Does not prevent route from being executed without authentication, but allows to extract it" in {
val request = Request[IO](Method.GET, Uri(path = "/private/private"))
val resp = routes.run(request).value.unsafeRunSync().getOrElse(Response.notFound)
if (resp.status == Status.Ok) {
val body = new String(resp.body.compile.toVector.unsafeRunSync().foldLeft(Array[Byte]())(_ :+ _))
body should_== "Test User at private"
} else ko(s"Invalid response code: ${resp.status}")
}
}
}

0 comments on commit 46d3787

Please sign in to comment.