Skip to content

Commit

Permalink
Merge pull request #170 from Open-MBEE/feat/locks-gsp
Browse files Browse the repository at this point in the history
feat: add head/get for locks gsp
  • Loading branch information
dlamoris authored Oct 16, 2024
2 parents 881f02a + 2c07da7 commit 1525038
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/org/openmbee/flexo/mms/routes/Locks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ fun Route.crudLocks() {
deleteLock()
}
}
}
}
24 changes: 22 additions & 2 deletions src/main/kotlin/org/openmbee/flexo/mms/routes/Model.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@ package org.openmbee.flexo.mms.routes.sparql
import com.linkedin.migz.MiGzOutputStream
import io.ktor.http.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import loadModel
import org.openmbee.flexo.mms.*
import org.openmbee.flexo.mms.routes.gsp.RefType
import org.openmbee.flexo.mms.server.graphStoreProtocol
import org.openmbee.flexo.mms.routes.gsp.readModel
import java.io.ByteArrayOutputStream



/**
* Model CRUD routing
*/
fun Route.crudModel() {
// by branch
graphStoreProtocol("/orgs/{orgId}/repos/{repoId}/branches/{branchId}/graph") {
// 5.6 HEAD: check state of graph
head {
readModel()
readModel(RefType.BRANCH)
}

// 5.2 GET: read graph
get {
readModel()
readModel(RefType.BRANCH)
}

// 5.3 PUT: overwrite (load)
Expand All @@ -46,6 +50,22 @@ fun Route.crudModel() {
//
// }
}

// by lock
graphStoreProtocol("/orgs/{orgId}/repos/{repoId}/locks/{lockId}/graph") {
// 5.6 HEAD: check state of graph
head {
readModel(RefType.LOCK)
}

// 5.2 GET: read graph
get {
readModel(RefType.LOCK)
}

// otherwise, deny the method
otherwiseNotAllowed("locks")
}
}


Expand Down
31 changes: 26 additions & 5 deletions src/main/kotlin/org/openmbee/flexo/mms/routes/gsp/ModelRead.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
package org.openmbee.flexo.mms.routes.gsp

import com.concurrentli.ManagedMultiBlocker.block
import io.ktor.http.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import org.openmbee.flexo.mms.*
import org.openmbee.flexo.mms.server.GspLayer1Context
import org.openmbee.flexo.mms.server.GspReadResponse

suspend fun GspLayer1Context<GspReadResponse>.readModel() {
enum class RefType {
BRANCH,
LOCK,
}

suspend fun GspLayer1Context<GspReadResponse>.readModel(refType: RefType) {
parsePathParams {
org()
repo()
branch()
when(refType) {
RefType.BRANCH -> branch()
RefType.LOCK -> lock()
}
}

val authorizedIri = "<${MMS_URNS.SUBJECT.auth}:${transactionId}>"
Expand All @@ -24,11 +34,17 @@ suspend fun GspLayer1Context<GspReadResponse>.readModel() {
""")
}
where {
auth(Permission.READ_BRANCH.scope.id, BRANCH_QUERY_CONDITIONS)
when(refType) {
RefType.BRANCH -> auth(Permission.READ_BRANCH.scope.id, BRANCH_QUERY_CONDITIONS)
RefType.LOCK -> auth(Permission.READ_LOCK.scope.id, LOCK_QUERY_CONDITIONS)
}

raw("""
graph mor-graph:Metadata {
morb: mms:commit/^mms:commit ?ref .
${when(refType) {
RefType.BRANCH -> "morb:"
RefType.LOCK -> "morl:"
}} mms:commit/^mms:commit ?ref .
?ref mms:snapshot ?modelSnapshot .
Expand Down Expand Up @@ -59,9 +75,14 @@ suspend fun GspLayer1Context<GspReadResponse>.readModel() {
throw Http403Exception(this, call.request.path())
}
}
// HEAD method
else if(call.request.httpMethod == HttpMethod.Head) {
call.respond(HttpStatusCode.OK)
}
// GET
else {
// try to avoid parsing model for performance reasons
val modelText = constructResponseText.replace("""$authorizedIri\s+<${MMS_URNS.PREDICATE.policy}>\s+\"(user|group)\"\s+\.""".toRegex(), "")
val modelText = constructResponseText.replace("""$authorizedIri\s+<${MMS_URNS.PREDICATE.policy}>\s+"(user|group)"\s+\.""".toRegex(), "")

call.respondText(modelText, contentType=RdfContentTypes.Turtle)
}
Expand Down
83 changes: 74 additions & 9 deletions src/main/kotlin/org/openmbee/flexo/mms/server/Protocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
protected val acceptableMediaTypesForPut: List<ContentType>?=acceptableMediaTypesForPost,
) {
// allowed HTTP methods for a resource matching the given route
protected var allowedMethods = mutableListOf("Options")
protected var allowedMethods = mutableListOf(HttpMethod.Options)

// accepted content types for a POST request to a resource matching the given route
protected var acceptedTypesPost = mutableListOf<ContentType>()
Expand Down Expand Up @@ -127,6 +127,24 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
layer1: Layer1Context<TRequestContext, TResponseContext>
) {}

protected suspend fun checkAllowed(
call: ApplicationCall,
resourcesLabel: String?=null
) {
// set allowed methods
call.response.headers.append("Allow", allowedMethods.joinToString(", ") { it.value })

// method not allowed
if(!allowedMethods.contains(call.request.httpMethod)) {
// reject request
call.respondText(
"${call.request.httpMethod.value} method not allowed on ${resourcesLabel ?: "this resource"}",
ContentType.Text.Plain,
HttpStatusCode.MethodNotAllowed
)
}
}

// applies custom before each and handles common
protected suspend fun <TResponseContext: GenericResponse> eachCall(
call: ApplicationCall,
Expand All @@ -144,8 +162,8 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
// invoke custom beforeEach if defined
beforeEach?.invoke(layer1)

// set allowed methods
call.response.headers.append("Allow", allowedMethods.joinToString { ", " })
// check allowed methods
checkAllowed(call)

// set accepted RDF formats for POST requests
if(acceptedTypesPost.isNotEmpty()) {
Expand Down Expand Up @@ -186,7 +204,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: ((Layer1Context<TRequestContext, TResponseContext>) -> Unit)?=null
) {
// add to allowed methods
allowedMethods.add("HEAD")
allowedMethods.add(HttpMethod.Head)

// allow implementor to handle when head is declared
declaredHead()
Expand All @@ -211,7 +229,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: ((Layer1Context<TRequestContext, TResponseContext>) -> Unit)?=null
) {
// add to allowed methods
allowedMethods.add("GET")
allowedMethods.add(HttpMethod.Get)

// allow implementor to handle when get is declared
declaredGet()
Expand All @@ -236,7 +254,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: ((Layer1Context<TRequestContext, TResponseContext>, slug: String) -> Unit)?=null
) {
// add to allowed methods
allowedMethods.add("POST")
allowedMethods.add(HttpMethod.Post)

// add supported RDF content types to accepted POST types
acceptedTypesPost.addAll(acceptableMediaTypesForPost)
Expand Down Expand Up @@ -272,7 +290,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: ((Layer1Context<TRequestContext, TResponseContext>) -> Unit)?=null
) {
// add to allowed methods
allowedMethods.add("PUT")
allowedMethods.add(HttpMethod.Put)

// add supported RDF content types to accepted PUT types
acceptedTypesPut.addAll(acceptableMediaTypesForPut ?: acceptableMediaTypesForPost)
Expand Down Expand Up @@ -304,7 +322,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: suspend PipelineContext<Unit, ApplicationCall>.(Layer1Context<TRequestContext, TResponseContext>) -> TUpdateRequest
) {
// add to allowed methods
allowedMethods.add("PATCH")
allowedMethods.add(HttpMethod.Patch)

// add supported RDF content types to accepted PATCH types
acceptedTypesPatch.addAll(acceptableMediaTypesForPatch)
Expand Down Expand Up @@ -332,7 +350,7 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
called: ((Layer1Context<TRequestContext, TResponseContext>) -> Unit)?=null
) {
// add to allowed methods
allowedMethods.add("DELETE")
allowedMethods.add(HttpMethod.Delete)

// define handler
route.delete {
Expand All @@ -350,4 +368,51 @@ abstract class GenericProtocolRoute<TRequestContext: GenericRequest>(
// OPTIONS
@Deprecated("Don't implement OPTIONS. Handled by wrapper")
fun options() {}

// adds "Method not allowed" handling for all other routes
fun otherwiseNotAllowed(
resourcesLabel: String?=null
) {
// HEAD
if(!allowedMethods.contains(HttpMethod.Head)) {
route.head {
checkAllowed(call, resourcesLabel)
}
}

// GET
if(!allowedMethods.contains(HttpMethod.Get)) {
route.get {
checkAllowed(call, resourcesLabel)
}
}

// POST
if(!allowedMethods.contains(HttpMethod.Post)) {
route.post {
checkAllowed(call, resourcesLabel)
}
}

// PUT
if(!allowedMethods.contains(HttpMethod.Put)) {
route.put {
checkAllowed(call, resourcesLabel)
}
}

// PATCH
if(!allowedMethods.contains(HttpMethod.Patch)) {
route.patch {
checkAllowed(call, resourcesLabel)
}
}

// DELETE
if(!allowedMethods.contains(HttpMethod.Delete)) {
route.delete {
checkAllowed(call, resourcesLabel)
}
}
}
}
79 changes: 79 additions & 0 deletions src/test/kotlin/org/openmbee/flexo/mms/ModelLoad.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.openmbee.flexo.mms

import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import io.ktor.http.*
import io.ktor.server.testing.*
import org.openmbee.flexo.mms.util.*

class ModelLoad : ModelAny() {
Expand Down Expand Up @@ -85,5 +88,81 @@ class ModelLoad : ModelAny() {
}
}
}

"head branch graph" {
commitModel(masterBranchPath, insertAliceRex)

withTest {
httpHead("$masterBranchPath/graph") {}.apply {
response shouldHaveStatus HttpStatusCode.OK
// response.content shouldBe null
}
}
}

"get branch graph" {
commitModel(masterBranchPath, insertAliceRex)

withTest {
httpGet("$masterBranchPath/graph") {}.apply {
response shouldHaveStatus HttpStatusCode.OK

response.exclusivelyHasTriples {
subjectTerse(":Alice") {
ignoreAll()
}

subjectTerse(":Rex") {
ignoreAll()
}
}
}
}
}

"head lock graph" {
commitModel(masterBranchPath, insertAliceRex)
createLock(demoRepoPath, masterBranchPath, demoLockId)

withTest {
httpHead("$demoLockPath/graph") {}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.content shouldBe null
}
}
}

"get lock graph" {
commitModel(masterBranchPath, insertAliceRex)
createLock(demoRepoPath, masterBranchPath, demoLockId)

withTest {
httpGet("$demoLockPath/graph") {}.apply {
response shouldHaveStatus HttpStatusCode.OK

response.exclusivelyHasTriples {
subjectTerse(":Alice") {
ignoreAll()
}

subjectTerse(":Rex") {
ignoreAll()
}
}
}
}
}

"lock graph rejects other methods" {
commitModel(masterBranchPath, insertAliceRex)
createLock(demoRepoPath, masterBranchPath, demoLockId)

withTest {
onlyAllowsMethods("$demoLockPath/graph", setOf(
HttpMethod.Head,
HttpMethod.Get,
))
}
}
}
}
Loading

0 comments on commit 1525038

Please sign in to comment.