From cb4b6b1aaba9e111a7f418471fc5c7466d9b3e37 Mon Sep 17 00:00:00 2001
From: Oliver <20188437+olivergrabinski@users.noreply.github.com>
Date: Fri, 13 Oct 2023 14:32:48 +0200
Subject: [PATCH] Add proxy pass route for id resolution (#4356)
---
delta/app/src/main/resources/app.conf | 2 +
.../ElasticSearchPluginModule.scala | 5 +-
.../routes/IdResolutionRoutes.scala | 27 +++++++-
.../routes/IdResolutionRoutesSuite.scala | 21 ++++++-
.../sdk/directives/DeltaDirectives.scala | 4 ++
.../nexus/delta/sdk/fusion/FusionConfig.scala | 4 +-
.../nexus/delta/sdk/ConfigFixtures.scala | 3 +-
.../sdk/directives/DeltaDirectivesSpec.scala | 3 +-
.../nexus/delta/sdk/utils/RouteFixtures.scala | 3 +-
.../api/assets/id-resolution/proxy-request.sh | 1 +
.../id-resolution/proxy-response-delta.html | 3 +
.../id-resolution/proxy-response-fusion.html | 3 +
.../paradox/docs/delta/api/id-resolution.md | 63 +++++++++++++++++++
.../nexus/tests/kg/IdResolutionSpec.scala | 36 +++++++++--
14 files changed, 162 insertions(+), 16 deletions(-)
create mode 100644 docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-request.sh
create mode 100644 docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-delta.html
create mode 100644 docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-fusion.html
diff --git a/delta/app/src/main/resources/app.conf b/delta/app/src/main/resources/app.conf
index 1417769624..e5c129917f 100644
--- a/delta/app/src/main/resources/app.conf
+++ b/delta/app/src/main/resources/app.conf
@@ -50,6 +50,8 @@ app {
# Allows to return a redirection when fetching a resource with the `Accept` header
# set to `text/html`
enable-redirects = false
+ # base to use to reconstruct resource identifiers in the proxy pass
+ resolve-base = "http://localhost:8081"
}
# Json-LD Api configuration
diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchPluginModule.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchPluginModule.scala
index 0d3f08a5d9..5eec1f42fe 100644
--- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchPluginModule.scala
+++ b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchPluginModule.scala
@@ -279,9 +279,10 @@ class ElasticSearchPluginModule(priority: Int) extends ModuleDef {
s: Scheduler,
ordering: JsonKeyOrdering,
rcr: RemoteContextResolution @Id("aggregate"),
- fusionConfig: FusionConfig
+ fusionConfig: FusionConfig,
+ baseUri: BaseUri
) =>
- new IdResolutionRoutes(identities, aclCheck, idResolution)(
+ new IdResolutionRoutes(identities, aclCheck, idResolution, baseUri)(
s,
ordering,
rcr,
diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutes.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutes.scala
index b29fa9e6db..2cb4d2f74b 100644
--- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutes.scala
+++ b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutes.scala
@@ -1,29 +1,34 @@
package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.routes
-import akka.http.scaladsl.model.Uri
+import akka.http.scaladsl.model.{StatusCodes, Uri}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.IdResolutionResponse.{MultipleResults, SingleResult}
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.query.ElasticSearchQueryError
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.{IdResolution, IdResolutionResponse}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
+import ch.epfl.bluebrain.nexus.delta.rdf.syntax.uriSyntax
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.directives.AuthDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.fusion.FusionConfig
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
+import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import monix.bio.{IO, UIO}
import monix.execution.Scheduler
class IdResolutionRoutes(
identities: Identities,
aclCheck: AclCheck,
- idResolution: IdResolution
+ idResolution: IdResolution,
+ baseUri: BaseUri
)(implicit s: Scheduler, jko: JsonKeyOrdering, rcr: RemoteContextResolution, fusionConfig: FusionConfig)
extends AuthDirectives(identities, aclCheck) {
- def routes: Route =
+ def routes: Route = concat(resolutionRoute, proxyRoute)
+
+ private def resolutionRoute: Route =
pathPrefix("resolve") {
extractCaller { implicit caller =>
(get & iriSegment & pathEndOrSingleSlash) { iri =>
@@ -37,6 +42,19 @@ class IdResolutionRoutes(
}
}
+ private def proxyRoute: Route =
+ pathPrefix("resolve-proxy-pass") {
+ extractUnmatchedPath { path =>
+ get {
+ val resourceId = fusionConfig.resolveBase / path
+ emitOrFusionRedirect(
+ fusionResolveUri(resourceId),
+ redirect(deltaResolveEndpoint(resourceId), StatusCodes.SeeOther)
+ )
+ }
+ }
+ }
+
private def fusionUri(
resolved: IO[ElasticSearchQueryError, IdResolutionResponse.Result]
): UIO[Uri] =
@@ -47,4 +65,7 @@ class IdResolutionRoutes(
}
.onErrorHandleWith { _ => fusionLoginUri }
+ private def deltaResolveEndpoint(id: Uri): Uri =
+ baseUri.endpoint / "resolve" / id.toString
+
}
diff --git a/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutesSuite.scala b/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutesSuite.scala
index 4a3d2f6cf0..0d9cf90fd0 100644
--- a/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutesSuite.scala
+++ b/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/IdResolutionRoutesSuite.scala
@@ -1,6 +1,7 @@
package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.routes
-import akka.http.scaladsl.model.StatusCodes
+import akka.http.scaladsl.model.headers.Location
+import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Route
import ch.epfl.bluebrain.nexus.delta.kernel.utils.UrlUtils
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.IdResolution
@@ -43,7 +44,8 @@ class IdResolutionRoutesSuite extends ElasticSearchViewsRoutesFixtures {
}
private val idResolution = new IdResolution(dummyDefaultViewsQuery, fetchResource)
- private val route = Route.seal(new IdResolutionRoutes(identities, aclCheck, idResolution).routes)
+ private val route =
+ Route.seal(new IdResolutionRoutes(identities, aclCheck, idResolution, baseUri).routes)
"The IdResolution route" should {
@@ -54,6 +56,21 @@ class IdResolutionRoutesSuite extends ElasticSearchViewsRoutesFixtures {
}
}
+ "redirect the proxy call to the resolve endpoint" in {
+ val segment = s"neurosciencegraph/data/$uuid"
+ val fullId = s"https://bbp.epfl.ch/$segment"
+ val expectedRedirection = s"$baseUri/resolve/${UrlUtils.encode(fullId)}".replace("%3A", ":")
+
+ Get(s"/resolve-proxy-pass/$segment") ~> route ~> check {
+ response.status shouldEqual StatusCodes.SeeOther
+ response.locationHeader shouldEqual expectedRedirection
+ }
+ }
+
+ }
+
+ implicit class HeaderOps(response: HttpResponse) {
+ def locationHeader: String = response.header[Location].value.uri.toString()
}
}
diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala
index 231fc1a0b6..31d1c20ffa 100644
--- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala
+++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala
@@ -180,6 +180,10 @@ trait DeltaDirectives extends UriDirectives {
def fusionLoginUri(implicit config: FusionConfig): UIO[Uri] =
UIO.pure { config.base / "login" }
+ /** The URI of fusion's id resolution endpoint */
+ def fusionResolveUri(id: Uri)(implicit config: FusionConfig): UIO[Uri] =
+ UIO.pure { config.base / "resolve" / id.toString }
+
/** Injects a `Vary: Accept,Accept-Encoding` into the response */
def varyAcceptHeaders: Directive0 =
vary(Set(Accept.name, `Accept-Encoding`.name))
diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/fusion/FusionConfig.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/fusion/FusionConfig.scala
index dac98c1a22..e6c3e50e6e 100644
--- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/fusion/FusionConfig.scala
+++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/fusion/FusionConfig.scala
@@ -11,8 +11,10 @@ import pureconfig.generic.semiauto.deriveReader
* the base url of fusion
* @param enableRedirects
* enables redirections to Fusion if the `Accept` header is set to `text/html`
+ * @param resolveBase
+ * base to use to reconstruct resource identifiers in the resolve proxy pass
*/
-final case class FusionConfig(base: Uri, enableRedirects: Boolean)
+final case class FusionConfig(base: Uri, enableRedirects: Boolean, resolveBase: Uri)
object FusionConfig {
implicit final val fusionConfigReader: ConfigReader[FusionConfig] =
diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/ConfigFixtures.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/ConfigFixtures.scala
index bad3229faa..ad20186094 100644
--- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/ConfigFixtures.scala
+++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/ConfigFixtures.scala
@@ -33,7 +33,8 @@ trait ConfigFixtures {
def httpClientConfig: HttpClientConfig =
HttpClientConfig(RetryStrategyConfig.AlwaysGiveUp, HttpClientWorthRetry.never, false)
- def fusionConfig: FusionConfig = FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true)
+ def fusionConfig: FusionConfig =
+ FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true, Uri("https://bbp.epfl.ch"))
def deletionConfig: ProjectsConfig.DeletionConfig = ProjectsConfig.DeletionConfig(
enabled = true,
diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala
index 90c12dad0f..2cc879d666 100644
--- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala
+++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala
@@ -57,7 +57,8 @@ class DeltaDirectivesSpec
List("@context", "@id", "@type", "reason", "details", "sourceId", "projectionId", "_total", "_results")
)
- implicit private val f: FusionConfig = FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true)
+ implicit private val f: FusionConfig =
+ FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true, Uri("https://bbp.epfl.ch"))
implicit val baseUri: BaseUri = BaseUri("http://localhost", Label.unsafe("v1"))
diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteFixtures.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteFixtures.scala
index 97aa1d58f2..e4a2d40c75 100644
--- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteFixtures.scala
+++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteFixtures.scala
@@ -56,7 +56,8 @@ trait RouteFixtures {
implicit val baseUri: BaseUri = BaseUri("http://localhost", Label.unsafe("v1"))
implicit val paginationConfig: PaginationConfig = PaginationConfig(5, 10, 5)
- implicit val f: FusionConfig = FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true)
+ implicit val f: FusionConfig =
+ FusionConfig(Uri("https://bbp.epfl.ch/nexus/web/"), enableRedirects = true, Uri("https://bbp.epfl.ch"))
implicit val s: Scheduler = Scheduler.global
implicit val rejectionHandler: RejectionHandler = RdfRejectionHandler.apply
implicit val exceptionHandler: ExceptionHandler = RdfExceptionHandler.apply
diff --git a/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-request.sh b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-request.sh
new file mode 100644
index 0000000000..ac76f7267c
--- /dev/null
+++ b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-request.sh
@@ -0,0 +1 @@
+curl "http://localhost:8080/v1/resolve-proxy-pass/nexus/data/identifier"
\ No newline at end of file
diff --git a/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-delta.html b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-delta.html
new file mode 100644
index 0000000000..538ed1d0a3
--- /dev/null
+++ b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-delta.html
@@ -0,0 +1,3 @@
+303 See Other
+The response to the request can be found under this URI using a
+GET method.
\ No newline at end of file
diff --git a/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-fusion.html b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-fusion.html
new file mode 100644
index 0000000000..f890c2a3d4
--- /dev/null
+++ b/docs/src/main/paradox/docs/delta/api/assets/id-resolution/proxy-response-fusion.html
@@ -0,0 +1,3 @@
+303 See Other
+The response to the request can be found under this URI using a
+GET method.
\ No newline at end of file
diff --git a/docs/src/main/paradox/docs/delta/api/id-resolution.md b/docs/src/main/paradox/docs/delta/api/id-resolution.md
index 99315ee696..d592f6ca9e 100644
--- a/docs/src/main/paradox/docs/delta/api/id-resolution.md
+++ b/docs/src/main/paradox/docs/delta/api/id-resolution.md
@@ -37,3 +37,66 @@ Response (single resource)
Response (multiple choices)
: @@snip [response.json](assets/id-resolution/multiple-resolution-response.json)
+### Fusion Redirection
+
+When querying the resolve endpoint, it is possible to add the `Accept: text/html` header in order for Nexus Delta to
+redirect to the appropriate Nexus Fusion page.
+
+## Resolve (Proxy Pass)
+
+@@@ note { .warning }
+
+This endpoint is designed for the Nexus deployment at the Blue Brain Project and as such might not suit the needs of
+other deployments.
+
+@@@
+
+The @ref:[resolve](#resolve) endpoint offers resource resolution on the resources the caller has access to. As such, if
+the client is a browser and does not have the ability to include an authorization header in the request, it is possible
+to use the proxy pass version of the resolve endpoint which will lead the client to a Nexus Fusion authentication page.
+This will allow to "inject" the user's token in a subsequent @ref:[resolve](#resolve) request made by Nexus Fusion.
+
+### Configuration
+
+* `app.fusion.base`: String - base URL for Nexus Fusion
+* `app.fusion.enable-redirects`: Boolean - needs to be `true` in order for redirects to work (defaults to `false`)
+* `app.fusion.resolve-base`: String - base URL to use when reconstructing the resource identifier in the
+ proxy pass endpoint
+
+### Redirection
+
+1. The client calls `/v1/resolve-proxy-pass/{segment}`
+2. Nexus Delta reconstructs the resource identifier
+ * `{resourceId}` = `{resolveBase}/{segment}`
+3. Nexus Delta redirects the client to...
+ * the `{fusionBaseUrl}/resolve/{resourceId}` Fusion endpoint if the `Accept: text/html` header is present
+ * the `/v1/resolve/{resourceId}` Delta endpoint otherwise
+
+The Nexus Fusion resolve page allows the user to authenticate (if they are not already authenticated) and will perform a
+call to the Nexus Delta `/v1/resolve/{resourceId}` with the user's authentication token.
+
+All calls to the `/v1/resolve-proxy-pass` endpoint lead to
+@link:[303 See Other responses](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303).
+
+### Example
+
+The example below assumes that:
+
+* `{fusionBaseUrl}` = `http://localhost:8080/fusion`
+* `{segment}` = `nexus/data/identifier`
+* `{resolveBase}` = `https://example.com`
+
+Request
+: @@snip [request.sh](assets/id-resolution/proxy-request.sh)
+
+Redirect (when `Acccept:text/html` is provided)
+: @@snip [response.json](assets/id-resolution/proxy-response-fusion.html)
+
+Redirect (no `Accept:text/html` provided)
+: @@snip [response.json](assets/id-resolution/proxy-response-delta.html)
+
+#### Remark
+
+In your networking setup, if a proxy pass is enabled to map `https://example.com/nexus/data/*`
+to `https://localhost:8080/v1/resolve-proxy-pass/nexus/data/*`, the proxy pass allows to de facto resolve resource with
+identifier of the type `https://example.com/nexus/data/*` by simply querying their `@id`.
\ No newline at end of file
diff --git a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/kg/IdResolutionSpec.scala b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/kg/IdResolutionSpec.scala
index 193d44bec9..779102370e 100644
--- a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/kg/IdResolutionSpec.scala
+++ b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/kg/IdResolutionSpec.scala
@@ -10,6 +10,7 @@ import ch.epfl.bluebrain.nexus.tests.BaseSpec
import ch.epfl.bluebrain.nexus.tests.Identity.listings.{Alice, Bob}
import ch.epfl.bluebrain.nexus.tests.iam.types.Permission.Organizations
import io.circe.Json
+import org.scalatest.Assertion
class IdResolutionSpec extends BaseSpec {
@@ -36,6 +37,11 @@ class IdResolutionSpec extends BaseSpec {
private val uniqueResourcePayload = resource(uniqueId)
private val reusedResourcePayload = resource(reusedId)
+ private val neurosciencegraphSegment = "neurosciencegraph/data/segment"
+ private val proxyIdBase = "http://localhost:8081"
+ private val neurosciencegraphId = s"$proxyIdBase/$neurosciencegraphSegment"
+ private val encodedNeurosciencegraphId = UrlUtils.encode(neurosciencegraphId)
+
private val unauthorizedAccessErrorPayload =
jsonContentOf("iam/errors/unauthorized-access.json")
@@ -93,30 +99,50 @@ class IdResolutionSpec extends BaseSpec {
}
"redirect to fusion login when if text/html accept header is present (no results)" in {
- val expectedRedirectUrl = "https://bbp.epfl.ch/nexus/web/login"
+ val fusionLoginPage = "https://bbp.epfl.ch/nexus/web/login"
deltaClient.get[String]("/resolve/unknownId", Bob, acceptTextHtml) { (_, response) =>
- response.status shouldEqual StatusCodes.SeeOther
- locationHeaderOf(response) shouldEqual expectedRedirectUrl
+ response isRedirectTo fusionLoginPage
}(PredefinedFromEntityUnmarshallers.stringUnmarshaller)
}
"redirect to fusion resource page if text/html accept header is present (single result)" in {
deltaClient.get[String](s"/resolve/$encodedUniqueId", Bob, acceptTextHtml) { (_, response) =>
- response.status shouldEqual StatusCodes.SeeOther
- locationHeaderOf(response) shouldEqual fusionResourcePageFor(encodedUniqueId)
+ response isRedirectTo fusionResourcePageFor(encodedUniqueId)
}(PredefinedFromEntityUnmarshallers.stringUnmarshaller)
}
"redirect to fusion resource selection page if text/html accept header is present (multiple result)" in { pending }
+ "redirect to delta resolve if the request comes to the proxy endpoint" in {
+ deltaClient.get[String](s"/resolve-proxy-pass/$neurosciencegraphSegment", Bob) { (_, response) =>
+ response isRedirectTo deltaResolveEndpoint(encodedNeurosciencegraphId)
+ }(PredefinedFromEntityUnmarshallers.stringUnmarshaller)
+ }
+
+ "redirect to fusion resolve if the request comes to the proxy endpoint with text/html accept header is present" in {
+ deltaClient.get[String](s"/resolve-proxy-pass/$neurosciencegraphSegment", Bob, acceptTextHtml) { (_, response) =>
+ response isRedirectTo fusionResolveEndpoint(encodedNeurosciencegraphId)
+ }(PredefinedFromEntityUnmarshallers.stringUnmarshaller)
+ }
+
}
+ implicit class HttpResponseOps(response: HttpResponse) {
+ def isRedirectTo(uri: String): Assertion = {
+ response.status shouldEqual StatusCodes.SeeOther
+ locationHeaderOf(response) shouldEqual uri
+ }
+ }
private def locationHeaderOf(response: HttpResponse) =
response.header[Location].value.uri.toString()
private def acceptTextHtml =
List(Accept(MediaRange.One(`text/html`, 1f)))
private def fusionResourcePageFor(encodedId: String) =
s"https://bbp.epfl.ch/nexus/web/$ref11/resources/$encodedId".replace("%3A", ":")
+ private def fusionResolveEndpoint(encodedId: String) =
+ s"https://bbp.epfl.ch/nexus/web/resolve/$encodedId".replace("%3A", ":")
+ private def deltaResolveEndpoint(encodedId: String) =
+ s"http://delta:8080/v1/resolve/$encodedId".replace("%3A", ":")
}