aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvlad <vlad@drivergrp.com>2016-08-02 10:54:00 -0700
committervlad <vlad@drivergrp.com>2016-08-02 10:54:00 -0700
commit3008753cf543caaecb7d0e325c9f4473ad8a0322 (patch)
treeb6ae10a3370ddc03c3bbbf58254fe6cd0e5b7d9e
parentdbe1579308b71b407393019103dd93c7fe6936c2 (diff)
downloaddriver-core-3008753cf543caaecb7d0e325c9f4473ad8a0322.tar.gz
driver-core-3008753cf543caaecb7d0e325c9f4473ad8a0322.tar.bz2
driver-core-3008753cf543caaecb7d0e325c9f4473ad8a0322.zip
Domain model responsible for auth is in core + Akka-http auth directives + More specific REST service API
-rw-r--r--src/main/scala/com/drivergrp/core/auth.scala100
-rw-r--r--src/main/scala/com/drivergrp/core/crypto.scala8
-rw-r--r--src/main/scala/com/drivergrp/core/file.scala12
-rw-r--r--src/main/scala/com/drivergrp/core/rest.scala79
4 files changed, 145 insertions, 54 deletions
diff --git a/src/main/scala/com/drivergrp/core/auth.scala b/src/main/scala/com/drivergrp/core/auth.scala
new file mode 100644
index 0000000..84d943d
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/auth.scala
@@ -0,0 +1,100 @@
+package com.drivergrp.core
+
+object auth {
+
+ final case class FullName[+T](firstName: Name[T], middleName: Name[T], lastName: Name[T])
+
+ final case class Email(username: String, domain: String) {
+ override def toString = username + "@" + domain
+ }
+
+ trait Role {
+ val id: Id[Role]
+ val name: Name[Role]
+
+ def canEditReport: Boolean = false
+ def canSignOffReport: Boolean = false
+ def canAssignRoles: Boolean = false
+ }
+
+ case object ObserverRole extends Role {
+ val id = Id(1L)
+ val name = Name("observer")
+ }
+
+ case object PatientRole extends Role {
+ val id = Id(2L)
+ val name = Name("patient")
+ }
+
+ case object CuratorRole extends Role {
+ val id = Id(3L)
+ val name = Name("curator")
+
+ override def canEditReport: Boolean = true
+ }
+
+ case object PathologistRole extends Role {
+ val id = Id(4L)
+ val name = Name("pathologist")
+
+ override def canEditReport: Boolean = true
+ override def canSignOffReport: Boolean = true
+ }
+
+ case object AdministratorRole extends Role {
+ val id = Id(5L)
+ val name = Name("administrator")
+
+ override def canEditReport: Boolean = true
+ override def canSignOffReport: Boolean = true
+ override def canAssignRoles: Boolean = true
+ }
+
+ final case class Avatar(id: Id[Avatar], name: Name[Avatar])
+
+ final case class User(id: Id[User], name: FullName[User], email: Email, avatar: Option[Avatar], roles: Set[Role])
+
+ val TestUser = User(Id[User](1L),
+ FullName[User](Name("James"), Name("Dewey"), Name("Watson")),
+ Email("j.watson", "uchicago.edu"),
+ Some(Avatar(Id[Avatar](1L), Name[Avatar]("Coolface"))),
+ Set(PathologistRole))
+
+ final case class Macaroon(value: String)
+
+ final case class Base64[T](value: String)
+
+ final case class AuthToken(value: Base64[Macaroon])
+
+ object directives {
+ import akka.http.scaladsl.server._
+ import Directives._
+
+ val AuthenticationTokenHeader = "WWW-Authenticate"
+
+ def authorize(role: Role): Directive1[Id[User]] = {
+ headerValueByName(AuthenticationTokenHeader).flatMap { tokenValue =>
+ val token = AuthToken(Base64[Macaroon](tokenValue))
+
+ extractUser(token) match {
+ case Some(user) =>
+ if (user.roles.contains(role)) provide(user.id)
+ else reject(ValidationRejection(s"User does not have the required ${role.name} role"))
+ case None =>
+ reject(ValidationRejection(s"Wasn't able to extract user for the token provided"))
+ }
+ }
+ }
+
+ def extractToken: Directive1[AuthToken] = {
+ headerValueByName(AuthenticationTokenHeader).flatMap { token =>
+ provide(AuthToken(Base64[Macaroon](token)))
+ }
+ }
+
+ def extractUser(authToken: AuthToken): Option[User] = {
+ Some(TestUser)
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/crypto.scala b/src/main/scala/com/drivergrp/core/crypto.scala
index 2910260..f693fa3 100644
--- a/src/main/scala/com/drivergrp/core/crypto.scala
+++ b/src/main/scala/com/drivergrp/core/crypto.scala
@@ -1,12 +1,8 @@
package com.drivergrp.core
-object crypto {
-
- final case class Macaroon(value: String)
+import com.drivergrp.core.auth.AuthToken
- final case class Base64[T](value: String)
-
- final case class AuthToken(value: Base64[Macaroon])
+object crypto {
final case class EncryptionKey(value: String)
diff --git a/src/main/scala/com/drivergrp/core/file.scala b/src/main/scala/com/drivergrp/core/file.scala
new file mode 100644
index 0000000..e9340ff
--- /dev/null
+++ b/src/main/scala/com/drivergrp/core/file.scala
@@ -0,0 +1,12 @@
+package com.drivergrp.core
+
+import com.drivergrp.core.time.Time
+
+object file {
+
+ final case class Document(
+ id: Id[Document],
+ name: Name[Document],
+ additionDate: Time
+ )
+}
diff --git a/src/main/scala/com/drivergrp/core/rest.scala b/src/main/scala/com/drivergrp/core/rest.scala
index 4edb466..ebb2640 100644
--- a/src/main/scala/com/drivergrp/core/rest.scala
+++ b/src/main/scala/com/drivergrp/core/rest.scala
@@ -2,14 +2,14 @@ package com.drivergrp.core
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
-import akka.http.scaladsl.marshalling.{Marshal, Marshaller}
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.RawHeader
-import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller}
+import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Flow
-import akka.util.{ByteString, Timeout}
-import com.drivergrp.core.crypto.{AuthToken, Crypto}
+import akka.util.ByteString
+import com.drivergrp.core.auth.AuthToken
+import com.drivergrp.core.crypto.Crypto
import com.drivergrp.core.logging.Logger
import com.drivergrp.core.stats.Stats
import com.drivergrp.core.time.TimeRange
@@ -18,12 +18,10 @@ import com.github.swagger.akka.model._
import com.github.swagger.akka.{HasActorSystem, SwaggerHttpService}
import com.typesafe.config.Config
-import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
-import scala.language.postfixOps
import scala.util.{Failure, Success}
-import scalaz.{Failure => _, Success => _}
import scalaz.Scalaz._
+import scalaz.{Failure => _, Success => _}
object rest {
@@ -32,29 +30,25 @@ object rest {
this.majorVersion === otherVersion.majorVersion
}
- trait Service {
+ type Service = AnyRef
- def sendRequest[I,O](authToken: AuthToken)(requestInput: I)
- (implicit marshaller: Marshaller[I, RequestEntity],
- unmarshaller: Unmarshaller[ResponseEntity, O]): Future[O]
+ trait ServiceTransport {
+
+ def sendRequest(authToken: AuthToken)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]]
}
- trait ServiceDiscovery {
+ trait ServiceDiscovery[T <: Service] {
- def discover(serviceName: Name[Service], version: ServiceVersion): Service
+ def discover(serviceName: Name[Service], version: ServiceVersion): T
}
- class HttpRestService(method: HttpMethod, uri: Uri, version: ServiceVersion,
- actorSystem: ActorSystem, executionContext: ExecutionContext,
- crypto: Crypto, log: Logger, stats: Stats, time: TimeProvider) extends Service {
+ class HttpRestServiceTransport(actorSystem: ActorSystem, executionContext: ExecutionContext,
+ crypto: Crypto, log: Logger, stats: Stats, time: TimeProvider) extends ServiceTransport {
protected implicit val materializer = ActorMaterializer()(actorSystem)
protected implicit val execution = executionContext
- protected implicit val timeout = Timeout(5 seconds)
- def sendRequest[I,O](authToken: AuthToken)(requestInput: I)
- (implicit marshaller: Marshaller[I, RequestEntity],
- unmarshaller: Unmarshaller[ResponseEntity, O]): Future[O] = {
+ def sendRequest(authToken: AuthToken)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]] = {
val requestTime = time.currentTime()
val encryptionFlow = Flow[ByteString] map { bytes =>
@@ -64,43 +58,32 @@ object rest {
ByteString(crypto.decrypt(crypto.keyForToken(authToken))(bytes.toArray))
}
- val response: Future[O] = for {
- requestData: RequestEntity <- Marshal(requestInput).to[RequestEntity](marshaller, executionContext)
- encryptedMessage = requestData.transformDataBytes(encryptionFlow)
- request: HttpRequest = buildRequest(authToken, requestData)
- _ = log.audit(s"Sending to ${request.uri} request $request")
- response <- Http()(actorSystem).singleRequest(request)(materializer)
- decryptedResponse = requestData.transformDataBytes(decryptionFlow)
- responseEntity <- Unmarshal(decryptedResponse).to[O](unmarshaller, executionContext, materializer)
- } yield {
- responseEntity
+ val request = requestStub
+ .withEntity(requestStub.entity.transformDataBytes(encryptionFlow))
+ .withHeaders(
+ RawHeader(auth.directives.AuthenticationTokenHeader, s"Macaroon ${authToken.value.value}"))
+
+ log.audit(s"Sending to ${request.uri} request $request")
+
+ val responseEntity = Http()(actorSystem).singleRequest(request)(materializer) map { response =>
+ if(response.status.isFailure()) throw new Exception("Http status is failure " + response.status)
+ else Unmarshal(response.entity.transformDataBytes(decryptionFlow))
}
- response.onComplete {
+ responseEntity.onComplete {
case Success(r) =>
val responseTime = time.currentTime()
- log.audit(s"Response from $uri to request $requestInput is successful")
- stats.recordStats(Seq("request", uri.toString, "success"), TimeRange(requestTime, responseTime), 1)
+ log.audit(s"Response from ${request.uri} to request $requestStub is successful")
+ stats.recordStats(Seq("request", request.uri.toString, "success"), TimeRange(requestTime, responseTime), 1)
case Failure(t: Throwable) =>
val responseTime = time.currentTime()
- log.audit(s"Failed to receive response from $uri of version $version to request $requestInput")
- log.error(s"Failed to receive response from $uri of version $version to request $requestInput", t)
- stats.recordStats(Seq("request", uri.toString, "fail"), TimeRange(requestTime, responseTime), 1)
+ log.audit(s"Failed to receive response from ${request.uri} to request $requestStub")
+ log.error(s"Failed to receive response from ${request.uri} to request $requestStub", t)
+ stats.recordStats(Seq("request", request.uri.toString, "fail"), TimeRange(requestTime, responseTime), 1)
} (executionContext)
- response
- }
-
- private def buildRequest(authToken: AuthToken, requestData: RequestEntity): HttpRequest = {
-
- HttpRequest(
- method, uri,
- headers = Vector(
- RawHeader("WWW-Authenticate", s"Macaroon ${authToken.value.value}"),
- RawHeader("Api-Version", version.majorVersion + "." + version.minorVersion)
- ),
- entity = requestData)
+ responseEntity
}
}