Skip to content

Document Eval Endpoint #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 153 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,153 @@
# evaluator
A remote Scala code evaluator
# Remote Scala Eval

The remote Scala evaluator is a server based application that
allows remote evaluation of arbitrary Scala code.

# Run from sources

```bash
sbt run
```

# Authentication

The remote Scala eval uses [JWT](https://jwt.io/) to encode / decode tokens.
The `secretKey` used for encoding/decoding is configurable as part of the service configuration in
`src/main/resources/application.conf`.

Please change `secretKey` by overriding it or providing the `EVAL_SECRET_KEY` env var.

```
eval.auth {
secretKey = "secretKey"
secretKey = ${?EVAL_SECRET_KEY}
}
```

## Generate an auth token

In order to generate an auth token you may use the scala console and invoke
the `org.scalaexercises.evaluator.auth#generateToken` like so

```
sbt console

scala> import org.scalaexercises.evaluator.auth._

scala> generateToken("your identity")
res0: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI
```

Note `your identity` is exclusively to identify incoming requests for logging purposes.
The Scala evaluator will authorize any incoming request generated with the `secretKey`

# Request

Requests are sent in JSON format via HTTP POST and are authenticated via the `x-scala-eval-api-token` header.

# Sample Request

Given the token above a sample request may look like:

```bash
curl -X POST -H "Content-Type: application/json" -H "x-scala-eval-api-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI" -d '{
"resolvers":[
"https://oss.sonatype.org/content/repositories/releases"
],
"dependencies":[
{
"groupId":"org.typelevel",
"artifactId":"cats-core_2.11",
"version":"0.4.1"
}
],
"code":"{import cats._; Monad[Id].pure(42)}"
}
' "http://localhost:8080/eval"
```

## Headers

```
Content-Type : application/json
x-scala-eval-api-token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eW91ciBpZGVudGl0eQ.cfH43Wa7k_w1i0W2pQhV1k21t2JqER9lw5EpJcENRMI
```

## Body

```json
{
"resolvers":[
"https://oss.sonatype.org/content/repositories/releases"
],
"dependencies":[
{
"groupId":"org.typelevel",
"artifactId":"cats-core_2.11",
"version":"0.4.1"
}
],
"code":"{import cats._; Monad[Id].pure(42)}"
}
```

- `resolvers` : A list of resolvers where artifacts dependencies are hosted
- `dependencies` : A List of artifacts required to eval the code
- `code` : Some Scala Code

## Response

After compiling and attempting to evaluate the server will return a response payload with the following fields:

- `msg` : A message indicating the result of the compilation and evaluation. Note failing to compile still yields http status 200 as the purpose of the service is to output a result without judging if the input was correct or not.
- `value` : The result of the evaluation or `null` if it didn't compile or an exception is thrown.
- `valueType` : The type of result or `null` if it didn't compile or an exception is thrown
- `compilationInfos` : A map of compilation severity errors and their associated message

For ilustration purposes here is a few Response Payload examples:

Successful compilation and Evaluation

```json
{
"msg": "Ok",
"value": "42",
"valueType": "java.lang.Integer",
"compilationInfos": {}
}
```

Compilation Failure

```json
{
"msg": "Compilation Error",
"value": null,
"valueType": null,
"compilationInfos": {
"ERROR": [
{
"message": "value x is not a member of cats.Monad[cats.Id]",
"pos": {
"start": 165,
"point": 165,
"end": 165
}
}
]
}
}
```

Evaluating code that may result in a thrown exception

```json
{
"msg": "Runtime Error",
"value": null,
"valueType": null,
"compilationInfos": {}
}
```


1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ enablePlugins(JavaAppPackaging)
addCompilerPlugin(
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
)

15 changes: 13 additions & 2 deletions src/main/scala/auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import org.http4s.util._
import scala.util.{Try, Success, Failure}
import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}

import org.log4s.getLogger

import scalaz.concurrent.Task

object auth {

private [this] val logger = getLogger

val config = ConfigFactory.load()

val SecretKeyPath = "eval.auth.secretKey"
Expand All @@ -20,6 +24,9 @@ object auth {
throw new IllegalStateException("Missing -Deval.auth.secretKey=[YOUR_KEY_HERE] or env var [EVAL_SECRET_KEY] ")
}

def generateToken(value : String = "{}") =
Jwt.encode(value, secretKey, JwtAlgorithm.HS256)

object `X-Scala-Eval-Api-Token` extends HeaderKey.Singleton {

type HeaderT = `X-Scala-Eval-Api-Token`
Expand All @@ -46,8 +53,12 @@ object auth {
req.headers.get(`X-Scala-Eval-Api-Token`) match {
case Some(header) =>
Jwt.decodeRaw(header.value, secretKey, Seq(JwtAlgorithm.HS256)) match {
case Success(_) => service(req)
case Failure(_) => Task.now(Response(Status.Unauthorized))
case Success(tokenIdentity) =>
logger.info(s"Auth success with identity : $tokenIdentity")
service(req)
case Failure(ex) =>
logger.warn(s"Auth failed : $ex")
Task.now(Response(Status.Unauthorized))
}
case None => Task.now(Response(Status.Unauthorized))
}
Expand Down