aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--src/main/scala/com/drivergrp/core/auth.scala68
-rw-r--r--src/main/scala/com/drivergrp/core/rest.scala4
-rw-r--r--src/test/scala/com/drivergrp/core/AuthTest.scala35
4 files changed, 73 insertions, 36 deletions
diff --git a/README.md b/README.md
index 25ada84..2bc5820 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,7 @@ For more examples check [project tests](https://github.com/drivergroup/driver-co
$ sbt publish-local
-3. TODO: Release new version of core:
+3. Release new version of core:
$ sbt release
diff --git a/src/main/scala/com/drivergrp/core/auth.scala b/src/main/scala/com/drivergrp/core/auth.scala
index 2d61cbf..6b8cdaa 100644
--- a/src/main/scala/com/drivergrp/core/auth.scala
+++ b/src/main/scala/com/drivergrp/core/auth.scala
@@ -3,6 +3,10 @@ package com.drivergrp.core
import akka.http.scaladsl.model.headers.HttpChallenges
import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
+import scala.concurrent.Future
+import scala.util.{Failure, Success, Try}
+import scalaz.OptionT
+
object auth {
sealed trait Permission
@@ -79,39 +83,55 @@ object auth {
final case class PasswordHash(value: String)
- def extractUser(authToken: AuthToken): User = {
- new User() {
- override def id: Id[User] = Id[User](1L)
- override def roles: Set[Role] = Set(PathologistRole)
- }
-
- // TODO: or reject(ValidationRejection(s"Wasn't able to extract user for the token provided")) if none
+ object AuthService {
+ val AuthenticationTokenHeader = "WWW-Authenticate"
}
- object directives {
+ trait AuthService[U <: User] {
+
import akka.http.scaladsl.server._
import Directives._
- val AuthenticationTokenHeader = "WWW-Authenticate"
+ protected def authStatus(authToken: AuthToken): OptionT[Future, U]
- def authorize(permission: Permission): Directive1[AuthToken] = {
+ def authorize(permission: Permission): Directive1[(AuthToken, U)] = {
parameters('authToken.?).flatMap { parameterTokenValue =>
- optionalHeaderValueByName(AuthenticationTokenHeader).flatMap { headerTokenValue =>
- headerTokenValue.orElse(parameterTokenValue) match {
- case Some(tokenValue) =>
- val token = AuthToken(Base64[Macaroon](tokenValue))
-
- if (extractUser(token).roles.exists(_.hasPermission(permission))) provide(token)
- else {
- val challenge = HttpChallenges.basic(s"User does not have the required permission $permission")
- reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
- }
-
- case None =>
- reject(MissingHeaderRejection("WWW-Authenticate"))
- }
+ optionalHeaderValueByName(AuthService.AuthenticationTokenHeader).flatMap { headerTokenValue =>
+ verifyAuthToken(headerTokenValue.orElse(parameterTokenValue), permission)
}
}
}
+
+ private def verifyAuthToken(tokenOption: Option[String], permission: Permission): Directive1[(AuthToken, U)] =
+ tokenOption match {
+ case Some(tokenValue) =>
+ val token = AuthToken(Base64[Macaroon](tokenValue))
+
+ onComplete(authStatus(token).run).flatMap { tokenUserResult =>
+ checkPermissions(tokenUserResult, permission, token)
+ }
+
+ case None =>
+ reject(MissingHeaderRejection(AuthService.AuthenticationTokenHeader))
+ }
+
+ private def checkPermissions(userResult: Try[Option[U]],
+ permission: Permission,
+ token: AuthToken): Directive1[(AuthToken, U)] = {
+ userResult match {
+ case Success(Some(user)) =>
+ if (user.roles.exists(_.hasPermission(permission))) provide(token -> user)
+ else {
+ val challenge = HttpChallenges.basic(s"User does not have the required permission $permission")
+ reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
+ }
+
+ case Success(None) =>
+ reject(ValidationRejection(s"Wasn't able to find authenticated user for the token provided"))
+
+ case Failure(t) =>
+ reject(ValidationRejection(s"Wasn't able to verify token for authenticated user", Some(t)))
+ }
+ }
}
}
diff --git a/src/main/scala/com/drivergrp/core/rest.scala b/src/main/scala/com/drivergrp/core/rest.scala
index 0d718c9..d97e13e 100644
--- a/src/main/scala/com/drivergrp/core/rest.scala
+++ b/src/main/scala/com/drivergrp/core/rest.scala
@@ -8,7 +8,7 @@ import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Flow
import akka.util.ByteString
-import com.drivergrp.core.auth.AuthToken
+import com.drivergrp.core.auth.{AuthService, AuthToken}
import com.drivergrp.core.crypto.Crypto
import com.drivergrp.core.logging.Logger
import com.drivergrp.core.stats.Stats
@@ -55,7 +55,7 @@ object rest {
val request = requestStub
.withEntity(requestStub.entity.transformDataBytes(encryptionFlow))
.withHeaders(
- RawHeader(auth.directives.AuthenticationTokenHeader, s"Macaroon ${authToken.value.value}"))
+ RawHeader(AuthService.AuthenticationTokenHeader, authToken.value.value))
log.audit(s"Sending to ${request.uri} request $request")
diff --git a/src/test/scala/com/drivergrp/core/AuthTest.scala b/src/test/scala/com/drivergrp/core/AuthTest.scala
index 992ae83..42f9155 100644
--- a/src/test/scala/com/drivergrp/core/AuthTest.scala
+++ b/src/test/scala/com/drivergrp/core/AuthTest.scala
@@ -9,13 +9,28 @@ import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsReject
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
+import scala.concurrent.Future
+import scalaz.OptionT
+
class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRouteTest {
+ val authStatusService: AuthService[User] = new AuthService[User] {
+ override def authStatus(authToken: AuthToken): OptionT[Future, User] = OptionT.optionT[Future] {
+ Future.successful(Some(new User() {
+ override def id: Id[User] = Id[User](1L)
+ override def roles: Set[Role] = Set(PathologistRole)
+ }))
+ }
+ }
+
+ import authStatusService._
+
"'authorize' directive" should "throw error is auth token is not in the request" in {
Get("/naive/attempt") ~>
- auth.directives.authorize(CanSignOutReport) { authToken =>
- complete("Never going to be here")
+ authorize(CanSignOutReport) {
+ case (authToken, user) =>
+ complete("Never going to be here")
} ~>
check {
handled shouldBe false
@@ -28,10 +43,11 @@ class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRo
val referenceAuthToken = AuthToken(Base64("I am a pathologist's token"))
Post("/administration/attempt").addHeader(
- RawHeader(auth.directives.AuthenticationTokenHeader, s"Macaroon ${referenceAuthToken.value.value}")
+ RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value)
) ~>
- auth.directives.authorize(CanAssignRoles) { authToken =>
- complete("Never going to get here")
+ authorize(CanAssignRoles) {
+ case (authToken, user) =>
+ complete("Never going to get here")
} ~>
check {
handled shouldBe false
@@ -47,14 +63,15 @@ class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRo
val referenceAuthToken = AuthToken(Base64("I am token"))
Get("/valid/attempt/?a=2&b=5").addHeader(
- RawHeader(auth.directives.AuthenticationTokenHeader, s"Macaroon ${referenceAuthToken.value.value}")
+ RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value)
) ~>
- auth.directives.authorize(CanSignOutReport) { authToken =>
- complete("Alright, \"" + authToken.value.value + "\" is handled")
+ authorize(CanSignOutReport) {
+ case (authToken, user) =>
+ complete("Alright, \"" + authToken.value.value + "\" is handled")
} ~>
check {
handled shouldBe true
- responseAs[String] shouldBe "Alright, \"Macaroon I am token\" is handled"
+ responseAs[String] shouldBe "Alright, \"I am token\" is handled"
}
}
}