Skip to content
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

ACL Link behavior update #3

Merged
merged 42 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5dae016
added ldes example - will need to be re-aranged
bblfish Mar 7, 2023
b6ed705
Develop LDES example tests
bblfish Mar 9, 2023
a17e9aa
added an example with a circularity and a test for it
bblfish Mar 10, 2023
3601838
Add test with links pointing nowhere
bblfish Mar 11, 2023
d43790d
Web implementation using http4s
bblfish Mar 13, 2023
4827974
added script to fetch ldes from reactive solid
bblfish Mar 15, 2023
f17df1f
stop sending Accept headers twice.
bblfish Mar 15, 2023
b6ef1a7
fetch with wallet updated
bblfish Mar 15, 2023
9c18d67
don't send Accept headers 2c acl req + reformat
bblfish Mar 16, 2023
e0f718c
seperate the crawling and the extracting
bblfish Mar 16, 2023
5c401cc
Add initial support for default ACLs
bblfish May 6, 2023
fd283dc
README thinking through data structures 4 web cache
bblfish May 9, 2023
8764e5a
more notes after looking at mules lib
bblfish May 10, 2023
e2e6658
update with final tagless idea from mules
bblfish May 11, 2023
7218707
add an advantage to DirTree mapping.
bblfish May 11, 2023
2225b20
link to cache rfc
bblfish May 11, 2023
ce79515
add simple tree based cache lib
bblfish May 14, 2023
5a0373f
add support for cache deletion
bblfish May 15, 2023
42a7501
move scripts to shared space
bblfish May 15, 2023
b939fde
copy of mules-http files by chrisdavenport
bblfish May 15, 2023
a6ea8ba
add .scalafmt from banana-rdf 3
bblfish May 15, 2023
c05858a
add CacheType too
bblfish May 16, 2023
285c82c
initial wallet with cache tests working
bblfish May 18, 2023
feeaca6
scalafmt added and applied everywhere
bblfish May 18, 2023
91794b6
initial tests for interpreted cache
bblfish May 19, 2023
2954d68
add Chris Davenport's license to his code
bblfish May 19, 2023
013acc6
add margins to Web strings
bblfish May 19, 2023
116a260
remove println
bblfish May 20, 2023
672c554
reformat some copyright info
bblfish May 20, 2023
e82f763
tests for getting & finding the cloest dir info
bblfish May 20, 2023
c94d63a
minor refactoring
bblfish May 20, 2023
aea15e2
MiniCV script works with cache (but not perfectly)
bblfish May 24, 2023
611dbf0
test cache non 2xx, simplify acl link following
bblfish May 25, 2023
d0e69d5
Caching of 401 responses & support for HEAD
bblfish May 26, 2023
34e7f0b
move chrisdavenport's Cache.scala into the project
bblfish May 26, 2023
b35d8c6
see https://discord.com/channels/632277896739946517/63227789744865284…
bblfish Jun 2, 2023
31de014
pre-sigining using cache (works but uses web)
bblfish Jun 2, 2023
7a3094e
Client now requires minimal over the wire http requests.
bblfish Jun 3, 2023
0960f23
scala-cli format .
bblfish Jun 5, 2023
bde9748
scaladoc format improvements
bblfish Jun 5, 2023
53c9e5f
update to scalafmt "3.7.4"
bblfish Jun 5, 2023
cb2643e
add loging for cache hits
bblfish Jun 7, 2023
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
47 changes: 43 additions & 4 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
version = 3.5.9
version = "3.7.4"
runner.dialect = scala3

indent {
main = 2
matchSite = 1
significant = 3
}
align {
preset = none
stripMargin = false
}
maxColumn = 100
assumeStandardLibraryStripMargin = true
rewrite.scala3 {
convertToNewSyntax = true
removeOptionalBraces = yes
}
runner.dialectOverride.allowQuestionMarkAsTypeWildcard = false
newlines {
selectChains = fold
beforeMultiline = fold
}
comments.wrapSingleLineMlcAsSlc = false
docstrings{
wrap = "no"
oneline = fold
style = SpaceAsterisk
}


fileOverride {
"glob:**.sbt" {
runner.dialect = scala212source3
}

"glob:**/project/**.scala" {
runner.dialect = scala212source3
}
"glob:**/interface/**.scala" {
runner.dialect = scala212source3
}

rewrite.rules = [Imports]
rewrite.imports.sort = scalastyle
"glob:**/sbt-plugin/**.scala" {
runner.dialect = scala212source3
}
}
4 changes: 2 additions & 2 deletions Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The server can signal its support for any of these via a [WWW-Authenticate](http
3. [HttpSig](https://github.com/bblfish/authentication-panel/blob/main/proposals/HttpSignature.md): the most efficient and secure authentication, best for connecting to other servers
4. UMA: as described in [Solid-OIDC](https://solidproject.org/TR/oidc-primer) is useful as a way of tying into widely deployed OAuth systems, but not efficient for highly decentralised apps.
5. Credential based - find the spec
6. Cookies: once a user is authenticated for a realm, using a cookie may be enough to continue the interaction.
6. Cookies: once a user is authenticated for a realm, using a cookie may be enough to continue the interaction. But cookie use in the browser by non-origin apps is limited and tricky.([see detailed course](https://www.youtube.com/watch?v=34wC1C61lg0))

We will get going with 1 and 3, but keeping 2 and 4 in mind will be helpful to make sure the architecture is correct. In any case the system should be extensible so that others can contribute other auth schemes without problem.

Expand Down Expand Up @@ -66,7 +66,7 @@ trait Client[F[_]]:
//...
```

This parallels the way Http4s defines server applications. As Ross Baker quipped in his [introductory talk]() ([video](https://www.youtube.com/watch?v=urdtmx4h5LE)):
This parallels the way Http4s defines server applications. As Ross Baker quipped in his introductory talk ([video](https://www.youtube.com/watch?v=urdtmx4h5LE)):

> HTTP applications are just a Kleisli function from a streaming request to a polymorphic effect of a streaming response. So what's the problem?

Expand Down
12 changes: 3 additions & 9 deletions app/src/main/scala/run/cosy/solid/app/http/Fetcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,15 @@ import org.http4s.implicits.*
import org.http4s.{ParseResult, QValue, Request, Uri, client}
import run.cosy.app.io.n3.N3Parser


import java.nio.charset.Charset
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}

import org.w3.banana.RDF
import org.w3.banana.RDF.*
import org.w3.banana.Ops

case class Fetcher[Rdf<:RDF](store: Store[Rdf])(using ops: Ops[Rdf]) {
import ops.{given,*}


}
case class Fetcher[Rdf <: RDF](store: Store[Rdf])(using ops: Ops[Rdf]):
import ops.{given, *}

@JSExportTopLevel("Fetcher")
object Fetcher {

}
object Fetcher {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@ package run.cosy.solid.app.http

// import org.http4s.headers.{Accept,*}

object RDFMediaTypes {

}
object RDFMediaTypes {}
4 changes: 1 addition & 3 deletions app/src/main/scala/run/cosy/solid/app/http/Web.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package run.cosy.solid.app.http

class Web {

}
class Web {}
134 changes: 69 additions & 65 deletions app/src/main/scala/solidapp/Example.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,72 @@ import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
@JSExportTopLevel("Example")
object Example:

import org.scalajs.dom
import dom.{document, html}

val clnt: client.Client[IO] = FetchClientBuilder[IO].create
// val rdfHeaders = org.http4s.Headers

import org.http4s.client.dsl.io.given
import org.http4s.headers.*

def main(args: Array[String]): Unit =
document.addEventListener("DOMContentLoaded", { (e: dom.Event) =>
println(e)
})

def setupUI(): Unit =
val button = document.createElement("button")
button.textContent = "Click me!"
button.addEventListener("click", { (e: dom.MouseEvent) =>
addClickedMessage()
})
document.body.appendChild(button)
appendPar(document.body, "Hello World")
end setupUI

@JSExport
def addClickedMessage(): Unit =
val url: ParseResult[Uri] = urlEntry()
url.fold(
fail => appendPar(document.body, "could not parse url " + fail),
uri => {
appendPar(document.body, "You clicked to fetch " + uri)
onClick(uri)
}
)

def urlEntry(): ParseResult[Uri] =
val urlStr = input.value
println("URL=" + urlStr)
Uri.fromString(urlStr)

def input: html.Input = document.getElementById("url").asInstanceOf[html.Input]

def onClick(uri: Uri): Unit =
val utfStr: fs2.Stream[cats.effect.IO, String] = clnt.stream(req(uri)).flatMap(_.body)
.through(text.utf8.decode)

val ios: fs2.Stream[cats.effect.IO, INothing] = utfStr.through(N3Parser.parse)
.chunks.foreach { chunk =>
IO(appendPar(document.body, s"chunk size ${chunk.size} starts with ${chunk.head}"))
}

ios.compile.lastOrError.unsafeRunAsync {
case Left(err) => appendPar(document.body, err.toString)
case Right(answer) => appendPar(document.body, "good answer")
}
end onClick

def req(uri: Uri): Request[IO] = GET(uri, Accept(turtle.withQValue(QValue.One)))

def appendPar(targetNode: dom.Node, text: String): Unit =
val parNode = document.createElement("p")
parNode.textContent = text
targetNode.appendChild(parNode)

end Example
import org.scalajs.dom
import dom.{document, html}

val clnt: client.Client[IO] = FetchClientBuilder[IO].create
// val rdfHeaders = org.http4s.Headers

import org.http4s.client.dsl.io.given
import org.http4s.headers.*

def main(args: Array[String]): Unit = document.addEventListener(
"DOMContentLoaded",
{ (e: dom.Event) =>
println(e)
}
)

def setupUI(): Unit =
val button = document.createElement("button")
button.textContent = "Click me!"
button.addEventListener(
"click",
{ (e: dom.MouseEvent) =>
addClickedMessage()
}
)
document.body.appendChild(button)
appendPar(document.body, "Hello World")
end setupUI

@JSExport
def addClickedMessage(): Unit =
val url: ParseResult[Uri] = urlEntry()
url.fold(
fail => appendPar(document.body, "could not parse url " + fail),
uri =>
appendPar(document.body, "You clicked to fetch " + uri)
onClick(uri)
)

def urlEntry(): ParseResult[Uri] =
val urlStr = input.value
println("URL=" + urlStr)
Uri.fromString(urlStr)

def input: html.Input = document.getElementById("url").asInstanceOf[html.Input]

def onClick(uri: Uri): Unit =
val utfStr: fs2.Stream[cats.effect.IO, String] = clnt.stream(req(uri)).flatMap(_.body)
.through(text.utf8.decode)

val ios: fs2.Stream[cats.effect.IO, INothing] = utfStr.through(N3Parser.parse).chunks
.foreach { chunk =>
IO(appendPar(document.body, s"chunk size ${chunk.size} starts with ${chunk.head}"))
}

ios.compile.lastOrError.unsafeRunAsync {
case Left(err) => appendPar(document.body, err.toString)
case Right(answer) => appendPar(document.body, "good answer")
}
end onClick

def req(uri: Uri): Request[IO] = GET(uri, Accept(turtle.withQValue(QValue.One)))

def appendPar(targetNode: dom.Node, text: String): Unit =
val parNode = document.createElement("p")
parNode.textContent = text
targetNode.appendChild(parNode)

end Example
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ import run.cosy.rdfjs.model.Quad

import scala.scalajs.js

class FetcherMunitTests extends munit.FunSuite:
Fetcher.setupUI()

class FetcherMunitTests extends munit.FunSuite {
Fetcher.setupUI()

test("HelloWorld") {
assert(document.querySelectorAll("p").count(_.textContent == "Hello World") == 1)
}

}
test("HelloWorld") {
assert(document.querySelectorAll("p").count(_.textContent == "Hello World") == 1)
}
51 changes: 30 additions & 21 deletions authn/shared/src/main/scala/net/bblfish/app/auth/AuthNClient.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Typelevel
* Copyright 2021 bblfish.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,34 +34,43 @@ import org.http4s.{BasicCredentials, Header, Request, Response, Status}
import scala.util.{Failure, Success, Try}

/** Client Authentication is a middleware that transforms a Client into a new Client that can use a
* Wallet to have requests signed.
* Wallet to have requests signed. It will try to sign a request
* 1. before it is sent using information it has available from the local cache on the server. So
* it will try to find an relevant ACL that it can use to determine if it can sign something
* 1. if the server returns a 401 it will use the response info to fetch the ACL rules and if it
* can, sign the request
*/
object AuthNClient:
def apply[F[_]: Concurrent](wallet: Wallet[F])(
client: Client[F]
): Client[F] =
def apply[F[_]: Concurrent](wallet: Wallet[F])(
client: Client[F]
): Client[F] =

def authLoop(
req: Request[F],
attempts: Int,
hotswap: Hotswap[F, Response[F]]
): F[Response[F]] =
hotswap.clear *> // Release the prior connection before allocating a new
def authLoop(
req: Request[F],
attempts: Int,
hotswap: Hotswap[F, Response[F]]
): F[Response[F]] = hotswap.clear *> // Release the prior connection before allocating a new
// todo: we should enhance the req with a signature if we already have info on the server
hotswap.swap(client.run(req)).flatMap { (resp: Response[F]) =>
// todo: may want a lot more flexibility than attempt numbering to determine if we should retry or not.
resp.status match
case Status.Unauthorized if attempts < 1 =>
wallet.sign(resp, req).flatMap(newReq => authLoop(newReq, attempts + 1, hotswap))
case _ => resp.pure[F]
case Status.Unauthorized if attempts < 1 =>
wallet.sign(resp, req).flatMap(newReq => authLoop(newReq, attempts + 1, hotswap))
case _ => resp.pure[F]
}

Client { req =>
// using the pattern from FollowRedirect example using Hotswap.
// Not 100% sure this is so much needed here...
Hotswap.create[F, Response[F]].flatMap { hotswap =>
Resource.eval(authLoop(req, 0, hotswap))
Client { req =>
// using the pattern from FollowRedirect example using Hotswap.
// Not 100% sure this is so much needed here...
Hotswap.create[F, Response[F]].flatMap { hotswap =>
Resource.eval(
wallet.signFromDB(req).map {
case Right(signedReq) => signedReq
case Left(_) => req
}.flatMap(req => authLoop(req, 0, hotswap))
)
}
}
}
end apply
end apply

end AuthNClient
Loading