Skip to content

Commit

Permalink
Parser and Tests for Rfc8941. + some Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
bblfish committed Apr 10, 2021
1 parent 0ed9a5b commit fe12b81
Show file tree
Hide file tree
Showing 25 changed files with 980 additions and 166 deletions.
6 changes: 2 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ lazy val root = project
.settings(
name := "cosy",
description := "Reactive Solid",
version := "0.1.0",
useScala3doc := true,

version := "0.1.2",
scalaVersion := Scala3Version,

//resolvers += Resolver.bintrayRepo("akka","snapshots"), //use if testing akka snapshots
resolvers += Resolver.sonatypeRepo("snapshots"), //for banana-rdf

libraryDependencies ++= dottyCompatLibs.map(_.withDottyCompat(scalaVersion.value)),
libraryDependencies ++= dottyCompatLibs,
libraryDependencies ++= javaLibs,
libraryDependencies ++= scala3Libs,

Expand Down
58 changes: 40 additions & 18 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sbt._
import sbt.{CrossVersion, _}

/**
* https://www.scala-sbt.org/1.x/docs/Organizing-Build.html
Expand All @@ -10,9 +10,8 @@ object Dependencies {
val circeVersion = "0.14.0-M4"
val bananaVersion = "0.9.0-SNAPSHOT"
val alpakkaVersion = "2.0.2"
val bouncyVersion = "1.68"

type NeedsDottyCompat = List[ModuleID]
val NeedsDottyCompat = List

//
// scala 2.13 libs
Expand All @@ -24,21 +23,21 @@ object Dependencies {
* @see https://akka.io
* @see https://repo1.maven.org/maven2/com/typesafe/akka
* */
val akka = NeedsDottyCompat("com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
"com.typesafe.akka" %% "akka-stream" % AkkaVersion,
"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
"com.typesafe.akka" %% "akka-slf4j" % AkkaVersion)
val akka = Seq("com.typesafe.akka" % "akka-actor-typed" % AkkaVersion,
"com.typesafe.akka" % "akka-stream" % AkkaVersion,
"com.typesafe.akka" % "akka-http" % AkkaHttpVersion,
"com.typesafe.akka" % "akka-slf4j" % AkkaVersion)

/**
* Apache 2 License
* @see https://doc.akka.io/docs/alpakka/current/
*/
val alpakka = "com.lightbend.akka" %% "akka-stream-alpakka-file" % alpakkaVersion
val alpakka = "com.lightbend.akka" % "akka-stream-alpakka-file" % alpakkaVersion


val akkaTest = NeedsDottyCompat("com.typesafe.akka" %% "akka-actor-testkit-typed" % AkkaVersion % Test,
"com.typesafe.akka" %% "akka-stream-testkit" % AkkaVersion % Test,
"com.typesafe.akka" %% "akka-http-testkit" % AkkaHttpVersion % Test)
val akkaTest = Seq("com.typesafe.akka" % "akka-actor-testkit-typed" % AkkaVersion % Test,
"com.typesafe.akka" % "akka-stream-testkit" % AkkaVersion % Test,
"com.typesafe.akka" % "akka-http-testkit" % AkkaHttpVersion % Test)

/**
* banana-rdf uses Scalaz so we won't use cats right now.
Expand All @@ -47,19 +46,28 @@ object Dependencies {
* @see https://scalaz.github.io/7/
* @see [[https://github.com/scalaz/scalaz/blob/master/LICENSE.txt License]]
*/
val scalaz = "org.scalaz" %% "scalaz-core" % scalazVersion
val scalaz = "org.scalaz" % "scalaz-core" % scalazVersion

/**
* banana-rdf is still using 2.13
* [[https://github.com/banana-rdf/banana-rdf/blob/series/0.8.x/LICENSE.md W3C License]]
* @see https://github.com/banana-rdf/banana-rdf
*/
val banana = NeedsDottyCompat(
"net.bblfish.rdf" %% "banana-rdf" % bananaVersion,
"net.bblfish.rdf" %% "banana-jena" % bananaVersion
val banana = Seq(
"net.bblfish.rdf" % "banana-rdf" % bananaVersion,
"net.bblfish.rdf" % "banana-jena" % bananaVersion
)

def dottyCompatLibs: NeedsDottyCompat = akka ++ akkaTest ++ banana ++ Seq(scalaz, alpakka)
/**
* MIT License
* @see https://github.com/typelevel/cats-parse
*/
val catsParse = "org.typelevel" %% "cats-parse" % "0.3.2"


def dottyCompatLibs = (Seq(scalaz, alpakka, catsParse) ++ akka ++ akkaTest ++ banana).map( o =>
o cross CrossVersion.for3Use2_13
)

//
// Scala 3 libs
Expand All @@ -79,6 +87,7 @@ object Dependencies {
val munit = "org.scalameta" %% "munit" % "0.7.23" % Test

val scala3Libs = Seq(scalatest, munit)

//
// Java Libs
//
Expand All @@ -102,7 +111,20 @@ object Dependencies {
* Apache 2 License
* @see https://connect2id.com/products/nimbus-jose-jwt/examples/jwk-conversion
*/
val nimbusDS = "com.nimbusds" % "nimbus-jose-jwt" % "9.7"
val nimbusDS = "com.nimbusds" % "nimbus-jose-jwt" % "9.8"

/**
* BouncyCastle (for parsing PEM encoded objects at present in test)
* MIT style License
* @see https://www.bouncycastle.org/latest_releases.html
* @see https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15to18/
*/
val bouncy = Seq(
//"org.bouncycastle" % "bcprov-jdk15to18" % bouncyVersion,
//"org.bouncycastle" % "bctls-jdk15to18" % bouncyVersion,
"org.bouncycastle" % "bcpkix-jdk15to18" % bouncyVersion % Test
)


/**
* License [[http://logback.qos.ch/license.html EPL v1.0 and the LGPL 2.1]]
Expand All @@ -117,7 +139,7 @@ object Dependencies {
*/
val apacheCommonsCodec = "commons-codec" % "commons-codec" % "1.15"

val javaLibs = Seq(tomitribeHttpSig, titaniumJSonLD, nimbusDS, logback, apacheCommonsCodec)
val javaLibs = Seq(tomitribeHttpSig, titaniumJSonLD, nimbusDS, logback, apacheCommonsCodec)++bouncy


}
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.4.7
sbt.version=1.5.0
10 changes: 9 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")
// no longer needed with sbt 1.5.0
//addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")

/**
* ScalaJS cross project sbt plugin
*
* @see https://github.com/portable-scala/sbt-crossproject
*/
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
23 changes: 12 additions & 11 deletions src/main/scala/run/cosy/Solid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import akka.http.scaladsl.server.{RequestContext, Route, RouteResult}
import akka.http.scaladsl.settings.ServerSettings
import akka.http.scaladsl.util.FastFuture
import akka.util.Timeout
import cats.data.NonEmptyList
import com.typesafe.config.{Config, ConfigFactory}
import org.w3.banana.PointedGraph
import run.cosy.http.{IResponse, RDFMediaTypes, RdfParser}
import run.cosy.http.auth.{HttpSig, SigningData}
import run.cosy.http.auth.HttpSig.{Agent, Anonymous, WebServerAgent}
import run.cosy.ldp.ResourceRegistry
import run.cosy.ldp.fs.BasicContainer
import run.cosy.ldp.fs.{BasicContainer => BC}
import scalaz.NonEmptyList
import scalaz.NonEmptyList.nel

import java.io.{File, FileInputStream}
import java.nio.file.{Files, Path}
Expand All @@ -49,7 +49,7 @@ object Solid {
given system: ActorSystem[Nothing] = ctx.system
given reg : ResourceRegistry = ResourceRegistry(ctx.system)
val withoutSlash = uri.withPath(uri.path.reverse.dropChars(1).reverse)
val rootRef: ActorRef[BasicContainer.Cmd] = ctx.spawn(BasicContainer(withoutSlash, fpath), "solid")
val rootRef: ActorRef[BC.Cmd] = ctx.spawn(BasicContainer(withoutSlash, fpath), "solid")
val registry = ResourceRegistry(system)
val solid = new Solid(uri, fpath, registry, rootRef)
given timeout: Scheduler = system.scheduler
Expand Down Expand Up @@ -143,18 +143,20 @@ class Solid(
baseUri: Uri,
path: Path,
registry: ResourceRegistry,
rootRef: ActorRef[BasicContainer.Cmd]
rootRef: ActorRef[BC.Cmd]
)(using sys: ActorSystem[_]) {

import akka.actor.typed.scaladsl.AskPattern.{Askable, schedulerFromActorSystem}
import run.cosy.http.auth.HttpSig
import run.cosy.http.headers.SigVerificationData
import akka.pattern.ask

import scala.concurrent.duration.*
import scala.jdk.CollectionConverters.*
given timeout: Scheduler = sys.scheduler
given scheduler: Timeout = Timeout(5.second)

def fetchKeyId(keyIdUrl: Uri)(reqc: RequestContext): Future[SigningData] = {
def fetchKeyId(keyIdUrl: Uri)(reqc: RequestContext): Future[SigVerificationData] = {
import RouteResult.{Complete,Rejected}
import run.cosy.RDF.{given,_}, run.cosy.RDF.ops.{given,*}
given ec: ExecutionContext = reqc.executionContext
Expand Down Expand Up @@ -187,13 +189,12 @@ class Solid(
val path = reqc.request.uri.path
import reqc.{given}
reqc.log.info("routing req " + reqc.request.uri)
val (remaining, actor): (List[String], ActorRef[BasicContainer.Cmd]) = registry.getActorRef(path)
val (remaining, actor): (List[String], ActorRef[BC.Cmd]) = registry.getActorRef(path)
.getOrElse((List[String](), rootRef))

def cmdFn(ref: ActorRef[HttpResponse]): BasicContainer.Cmd = remaining match {
case Nil => BasicContainer.Do(reqc.request, ref)
case head :: tail => BasicContainer.Route(NonEmptyList(head, tail), reqc.request, ref)
}
def cmdFn(ref: ActorRef[HttpResponse]): BC.Cmd = remaining match
case Nil => BC.WannaDo(agent, reqc.request, ref)
case head :: tail => BC.RouteMsg(NonEmptyList.fromSeq(head,tail.toSeq), agent, reqc.request, ref)

actor.ask[HttpResponse](cmdFn).map(RouteResult.Complete(_))
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/run/cosy/http/RDFMediaTypes.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package run.cosy.http

import akka.actor.typed.ActorSystem
import akka.http.scaladsl.model.MediaTypes
import akka.http.scaladsl.model.{MediaRange, MediaTypes}
import akka.http.scaladsl.model.MediaTypes.extensionMap
import akka.http.scaladsl.settings.ParserSettings

Expand Down Expand Up @@ -51,7 +51,7 @@ object RDFMediaTypes {
* see [[https://www.w3.org/TR/turtle/ RDF 1.1 Turtle]]
* has URI: http://www.w3.org/ns/formats/Turtle
*/
val `text/turtle` = MediaType.customWithFixedCharset(
val `text/turtle`: MediaType.WithFixedCharset = MediaType.customWithFixedCharset(
"text","turtle",`UTF-8`,
fileExtensions = List("ttl")
)
Expand Down Expand Up @@ -115,7 +115,7 @@ object RDFMediaTypes {
def rdfData: Seq[MediaType] = Seq(`application/rdf+xml`, `application/n-triples`, `application/n-quads`,
`text/n-quads`,`text/turtle`,`application/trig`, `text/n3`,`application/ld+json`
)

def all: Seq[MediaType] = Seq(`application/rdf+xml`, `application/n-triples`, `application/n-quads`,
`text/n-quads`,`text/turtle`,`application/trig`, `text/n3`,`application/ld+json`,
`application/sparql-results+json`, `application/sparql-results+xml`, `application/trix`
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/run/cosy/http/RdfParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import scala.concurrent.{ExecutionContext, Future}
import org.w3.banana._
import org.w3.banana.syntax._
import org.w3.banana.jena.Jena
import Jena._
import Jena.ops._
import akka.http.scaladsl.util.FastFuture
import akka.stream.Materializer
import org.apache.jena.graph.Graph
Expand All @@ -18,6 +16,8 @@ import scala.util.{Failure, Try}
import scala.util.control.NoStackTrace

object RdfParser {
import run.cosy.RDF.{given,*}
import ops.{given,*}
import RDFMediaTypes.*

/** @param base: the URI at which the document was resolved */
Expand Down
16 changes: 8 additions & 8 deletions src/main/scala/run/cosy/http/auth/HttpSig.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package run.cosy.http.auth

import akka.http.scaladsl.model.{HttpEntity, HttpRequest, Uri}
import akka.http.scaladsl.model.headers.{Authorization, GenericHttpCredentials, HttpChallenge, HttpCredentials,
OAuth2BearerToken, `Content-Type`}
import akka.http.scaladsl.model.{HttpEntity, HttpHeader, HttpRequest, Uri}
import akka.http.scaladsl.model.headers.{Authorization, GenericHttpCredentials, HttpChallenge, HttpCredentials, OAuth2BearerToken, `Content-Type`}
import akka.http.scaladsl.server.AuthenticationFailedRejection.{CredentialsMissing, CredentialsRejected}
import akka.http.scaladsl.server.{AuthenticationFailedRejection, Directive1, RequestContext}
import akka.http.scaladsl.server.Directives.{AsyncAuthenticator, AuthenticationResult,
authenticateOrRejectWithChallenge, extract, extractCredentials, extractRequestContext, provide}
import akka.http.scaladsl.server.Directives.{AsyncAuthenticator, AuthenticationResult, authenticateOrRejectWithChallenge, extract, extractCredentials, extractRequestContext, provide}
import akka.http.scaladsl.server.directives.{AuthenticationDirective, AuthenticationResult, Credentials}
import akka.http.scaladsl.server.directives.BasicDirectives.extractExecutionContext
import akka.http.scaladsl.server.directives.RouteDirectives.reject
import akka.http.scaladsl.util.FastFuture
import com.nimbusds.jose.jwk.JWK
import org.tomitribe.auth.signatures.{Algorithm, Signatures, Signer, SigningAlgorithm, Verifier}
import run.cosy.http.headers.SigVerificationData
import run.cosy.http.{InvalidCreatedFieldException, InvalidExpiresFieldException}

import java.net.URI
import java.security.{PublicKey, Signature}
import java.security.{PrivateKey, PublicKey, Signature}
import java.time.{Clock, Instant}
import java.util
import java.util.{Locale, Map}
Expand Down Expand Up @@ -59,11 +58,11 @@ object HttpSig {
*
* use with [[akka.http.scaladsl.server.directives.SecurityDirectives.authenticateOrRejectWithChallenge]]
*/
def httpSignature(reqc: RequestContext)(fetch: Uri => Future[SigningData]): AuthenticationDirective[Agent] =
def httpSignature(reqc: RequestContext)(fetch: Uri => Future[SigVerificationData]): AuthenticationDirective[Agent] =
authenticateOrRejectWithChallenge(httpSigAuthN(reqc.request)(fetch)(using reqc.executionContext))

def httpSigAuthN(req: HttpRequest)(
fetch: Uri => Future[SigningData])(
fetch: Uri => Future[SigVerificationData])(
using ec: ExecutionContext
): Option[HttpCredentials] => Future[AuthenticationResult[Agent]] =
case Some(c@GenericHttpCredentials("Signature",_,params)) =>
Expand Down Expand Up @@ -121,6 +120,7 @@ class HttpSig(
signatureExpiration: scala.Option[Long]
) {
import scala.jdk.CollectionConverters.SeqHasAsJava

def createSigningString(req: HttpRequest): Try[String] =
val headersMap = req.headers.foldRight(new java.util.HashMap[String,String]()){
(h,m) => m.put(h.name(),h.value); m}
Expand Down
15 changes: 4 additions & 11 deletions src/main/scala/run/cosy/http/auth/JW2JCA.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import com.nimbusds.jose.crypto.impl.RSASSA
import com.nimbusds.jose.jwk.{AsymmetricJWK, ECKey, JWK, RSAKey}
import com.nimbusds.jose.util.Base64
import run.cosy.http.CryptoException
import run.cosy.http.headers.SigVerificationData

import java.nio.charset.{Charset, StandardCharsets}
import java.security.{Provider, PublicKey, Signature}
import java.security.{PrivateKey, Provider, PublicKey, Signature}
import scala.util.{Failure, Try}

/**
Expand All @@ -17,12 +18,12 @@ import scala.util.{Failure, Try}
object JW2JCA {
val signerFactory = new DefaultJWSSignerFactory()

def jw2rca(jwk: JWK): Try[SigningData] = {
def jw2rca(jwk: JWK): Try[SigVerificationData] = {
jwk.getAlgorithm match {
case jwsAlgo: JWSAlgorithm =>
Try(RSASSA.getSignerAndVerifier(jwsAlgo,signerFactory.getJCAContext.getProvider)).flatMap{sig=>
jwk match
case k: AsymmetricJWK => Try(SigningData(k.toPublicKey, sig))
case k: AsymmetricJWK => Try(SigVerificationData(k.toPublicKey, sig))
case _ => Failure(CryptoException("we only use assymetric keys!"))
}
case alg => Failure(CryptoException("We do not support algorithm "+alg))
Expand All @@ -42,11 +43,3 @@ object JW2JCA {

}

case class SigningData(pubKey: PublicKey, sig: Signature) {
//this is not thread safe!
def verifySignature(signingStr: String) = (base64SigStr: String) =>
sig.initVerify(pubKey)
sig.update(signingStr.getBytes(StandardCharsets.US_ASCII))
sig.verify(new Base64(base64SigStr).decode())
}

3 changes: 2 additions & 1 deletion src/main/scala/run/cosy/http/auth/JWKExtractor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import scala.util.control.NoStackTrace
import RDF.{given,*}, ops.given

object JWKExtractor {
//import io.circe.*, io.circe.parser.*

import jwk.{JWK, JWKMatcher, KeyUse, OctetSequenceKey}
import recordBinder.*

Expand Down Expand Up @@ -65,5 +65,6 @@ object JWkey {
import org.tomitribe.auth.signatures.Algorithm
scala.util.Try(jwk.JWK.parse(jwkStr))
}

}

2 changes: 1 addition & 1 deletion src/main/scala/run/cosy/http/auth/SecurityPrefix.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object SecurityPrefix {
}


class SecurityPrefix[Rdf <: RDF](ops: RDFOps[Rdf]) extends PrefixBuilder("security", "https://w3id.org/security/v1")(ops) {
class SecurityPrefix[Rdf <: RDF](ops: RDFOps[Rdf]) extends PrefixBuilder("security", "https://w3id.org/security/v1#")(ops) {
val controller = apply("controller")
val publicKeyJwk = apply("publicKeyJwk")
}
Loading

0 comments on commit fe12b81

Please sign in to comment.