Skip to content

Commit

Permalink
Merge pull request #22 from metaschema-framework/feature/metapath-query
Browse files Browse the repository at this point in the history
add file upload capability
  • Loading branch information
wandmagic authored Oct 23, 2024
2 parents e65fc2d + 0c00d93 commit 06e0d96
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 44 deletions.
208 changes: 167 additions & 41 deletions src/main/kotlin/gov/nist/secauto/oscal/tools/server/OscalVerticle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ class OscalVerticle : CoroutineVerticle() {
routerBuilder.operation("validateUpload").handler { ctx -> handleValidateFileUpload(ctx) }
routerBuilder.operation("validate").handler { ctx -> handleValidateRequest(ctx) }
routerBuilder.operation("resolve").handler { ctx -> handleResolveRequest(ctx) }
routerBuilder.operation("resolveUpload").handler { ctx -> handleResolveFileUpload(ctx) }
routerBuilder.operation("convert").handler { ctx -> handleConvertRequest(ctx) }
routerBuilder.operation("convertUpload").handler { ctx -> handleConvertFileUpload(ctx) }
routerBuilder.operation("query").handler { ctx -> handleQueryRequest(ctx) }
routerBuilder.operation("queryUpload").handler { ctx -> handleQueryFileUpload(ctx) }
routerBuilder.operation("healthCheck").handler { ctx -> handleHealthCheck(ctx) }

val router = routerBuilder.createRouter()
Expand All @@ -96,20 +99,18 @@ class OscalVerticle : CoroutineVerticle() {
}

private fun handleQueryRequest(ctx: RoutingContext) {
launch {
launch {
try {
logger.info("Handling Query request")
val encodedContent = ctx.queryParam("document").firstOrNull()
val expression = ctx.queryParam("expression").firstOrNull()
if (encodedContent != null&&expression!=null) {
if (encodedContent != null && expression != null) {
val content = processUrl(encodedContent)
val args = mutableListOf("query")
args.add("-i")
args.add(content)
args.add("-e")
args.add(expression)
args.add("-m")
args.add("https://raw.githubusercontent.com/usnistgov/OSCAL/refs/heads/main/src/metaschema/oscal_complete_metaschema.xml")
val result = async {
try {
executeCommand(args)
Expand All @@ -128,9 +129,10 @@ class OscalVerticle : CoroutineVerticle() {
sendErrorResponse(ctx, 500, "Internal server error")
}
}

}



private fun processUrl(url: String): String {
logger.info("processUrl input: $url")

Expand Down Expand Up @@ -188,7 +190,7 @@ class OscalVerticle : CoroutineVerticle() {
// Write the body content to the temporary file
tempFile.appendText(body)
logger.info("Wrote body content to temporary file")

logger.info(tempFilePath+" :tempfilepath");
// Use async for parallelism
val result = async {
executeCommand(listOf("validate", tempFilePath.toString(),"--show-stack-trace"))
Expand Down Expand Up @@ -288,48 +290,49 @@ class OscalVerticle : CoroutineVerticle() {
}
}
}
private fun mapMimeTypeToFormat(mimeType: String?, formatParam: String?): String {
// Check if a valid format parameter is provided
if (!formatParam.isNullOrBlank()) {
return when (formatParam.lowercase()) {
"json" -> "JSON"
"xml" -> "XML"
"yaml" -> "YAML"
else -> "JSON" // Default to JSON if an invalid format is provided
}
}

// If no valid format parameter, check the MIME type
mimeType?.lowercase()?.let {
return when {
it.contains("json") -> "JSON"
it.contains("xml") -> "XML"
it.contains("yaml") -> "YAML"
else -> "JSON" // Default to JSON if no valid MIME type is provided
}
private fun mapMimeTypeToFormat(mimeType: String?, formatParam: String?): String {
// Check if a valid format parameter is provided
if (!formatParam.isNullOrBlank()) {
return when (formatParam.lowercase()) {
"json" -> "JSON"
"xml" -> "XML"
"yaml" -> "YAML"
else -> "ERROR"
}
}

// Default to JSON if neither a valid formatParam nor MIME type is provided
return "JSON"
// If no valid format parameter, check the MIME type
mimeType?.lowercase()?.let {
return when {
it.contains("json") -> "JSON"
it.contains("xml") -> "XML"
it.contains("yaml") -> "YAML"
else -> "ERROR" // Default to JSON if no valid MIME type is provided
}
}

// Default to JSON if neither a valid formatParam nor MIME type is provided
return "ERROR"
}

private fun mapFormatToMimeType(format: String?): String {
return when (format) {
"JSON"->"application/json"
"XML"->"text/xml"
"YAML" -> "text/yaml"
else -> "application/json" // Default to JSON if no valid MIME type is provided
}

private fun mapFormatToMimeType(format: String?): String {
return when (format) {
"JSON"->"application/json"
"XML"->"text/xml"
"YAML" -> "text/yaml"
else -> "ERROR" // Default to JSON if no valid MIME type is provided
}
private fun flagToParam(format: String): String {
return when (format) {
"disable-schema"->"--disable-schema-validation"
"disable-constraint"->"--disable-constraint-validation"
else -> "--quiet" // Default to JSON if no valid MIME type is provided
}
}
private fun flagToParam(format: String): String {
return when (format) {
"disable-schema"->"--disable-schema-validation"
"disable-constraint"->"--disable-constraint-validation"
else -> "--quiet"
}
private fun handleConvertRequest(ctx: RoutingContext) {
}
private fun handleConvertRequest(ctx: RoutingContext) {
launch {
try {
val encodedContent = ctx.queryParam("document").firstOrNull()
Expand All @@ -355,6 +358,8 @@ class OscalVerticle : CoroutineVerticle() {
}
}



private fun parseCommandToArgs(command: String): List<String> {
return command.split("\\s+".toRegex()).filter { it.isNotBlank() }
}
Expand Down Expand Up @@ -451,4 +456,125 @@ class OscalVerticle : CoroutineVerticle() {
vertx.deployVerticle(OscalVerticle())
}
}
}
private fun handleQueryFileUpload(ctx: RoutingContext) {
launch {
try {
logger.info("Handling Query file upload request")
val body = ctx.body().asString()
val expression = ctx.queryParam("expression").firstOrNull()

if (body.isNotEmpty() && expression != null) {
// Create a temporary file
val tempFile = Files.createTempFile(oscalDir, "upload", ".tmp")
val tempFilePath = tempFile.toAbsolutePath()
logger.info("Created temporary file: $tempFilePath")

// Write the body content to the temporary file
tempFile.appendText(body)
logger.info("Wrote body content to temporary file")

val args = mutableListOf("query")
args.add("-i")
args.add(processUrl(tempFilePath.toString()))
args.add("-e")
args.add(expression)

val result = async {
try {
executeCommand(args)
} catch (e: Exception) {
logger.error("Error handling request", e)
executeCommand(args)
}
}.await()

logger.info(result.second)
sendSuccessResponse(ctx, result.first, result.second)

// Clean up temporary file
} else {
sendErrorResponse(ctx, 400, "Missing request body or expression parameter")
}
} catch (e: Exception) {
logger.error("Error handling file upload request", e)
sendErrorResponse(ctx, 500, "Internal server error")
}
}
}

private fun handleResolveFileUpload(ctx: RoutingContext) {
launch {
try {
logger.info("Handling Resolve file upload request")
val body = ctx.body().asString()
val acceptHeader = ctx.request().getHeader("Accept")
val formatParam = ctx.queryParam("format").firstOrNull()
val format = mapMimeTypeToFormat(acceptHeader, formatParam)

if (body.isNotEmpty()) {
// Create a temporary file
val tempFile = Files.createTempFile(oscalDir, "upload", ".tmp")
val tempFilePath = tempFile.toAbsolutePath()
logger.info("Created temporary file: $tempFilePath")

// Write the body content to the temporary file
tempFile.appendText(body)
logger.info("Wrote body content to temporary file")

val result = async {
executeCommand(parseCommandToArgs("resolve-profile ${processUrl(tempFilePath.toString())} --to=$format"))
}.await()

logger.info(result.second)
ctx.response().putHeader("Content-Type", mapFormatToMimeType(format))
sendSuccessResponse(ctx, result.first, result.second)

// Clean up temporary file
} else {
sendErrorResponse(ctx, 400, "No content in request body")
}
} catch (e: Exception) {
logger.error("Error handling file upload request", e)
sendErrorResponse(ctx, 500, "Internal server error")
}
}
}

private fun handleConvertFileUpload(ctx: RoutingContext) {
launch {
try {
logger.info("Handling Convert file upload request")
val body = ctx.body().asString()
val acceptHeader = ctx.request().getHeader("Accept")
val formatParam = ctx.queryParam("format").firstOrNull()
val format = mapMimeTypeToFormat(acceptHeader, formatParam)

if (body.isNotEmpty()) {
// Create a temporary file
val tempFile = Files.createTempFile(oscalDir, "upload", ".tmp")
val tempFilePath = tempFile.toAbsolutePath()
logger.info("Created temporary file: $tempFilePath")

// Write the body content to the temporary file
tempFile.appendText(body)
logger.info("Wrote body content to temporary file")

val result = async {
executeCommand(parseCommandToArgs("convert ${processUrl(tempFilePath.toString())} --to=$format"))
}.await()

logger.info(result.second)
ctx.response().putHeader("Content-Type", mapFormatToMimeType(format))
sendSuccessResponse(ctx, result.first, result.second)

// Clean up temporary file
} else {
sendErrorResponse(ctx, 400, "No content in request body")
}
} catch (e: Exception) {
logger.error("Error handling file upload request", e)
sendErrorResponse(ctx, 500, "Internal server error")
}
}
}
}
81 changes: 78 additions & 3 deletions src/main/resources/webroot/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ paths:
items:
type: string
enum: [disable-schema, disable-constraint]

description: URIs of metaschema extension modules to load
example: disable-schema
responses:
Expand Down Expand Up @@ -121,6 +120,32 @@ paths:
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
post:
operationId: resolveUpload
summary: Resolve uploaded OSCAL document
parameters:
- in: query
name: format
required: false
schema:
type: string
enum: [json, yaml, xml]
description: Specify the format of the response
example: json
requestBody:
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
$ref: '#/components/responses/OscalResponse'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'

/convert:
get:
Expand Down Expand Up @@ -149,6 +174,32 @@ paths:
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
post:
operationId: convertUpload
summary: Convert uploaded OSCAL document
parameters:
- in: query
name: format
required: false
schema:
type: string
enum: [json, yaml, xml]
description: Specify the format of the response
example: json
requestBody:
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
$ref: '#/components/responses/OscalResponse'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
/query:
get:
operationId: query
Expand All @@ -169,7 +220,31 @@ paths:
type: string
description: metapath expression to query oscal document
example: //user

responses:
'200':
$ref: '#/components/responses/OscalResponse'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
post:
operationId: queryUpload
summary: Query uploaded OSCAL document
parameters:
- in: query
name: expression
required: true
schema:
type: string
description: metapath expression to query oscal document
example: //user
requestBody:
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
$ref: '#/components/responses/OscalResponse'
Expand Down Expand Up @@ -238,4 +313,4 @@ components:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: '#/components/schemas/Error'

0 comments on commit 06e0d96

Please sign in to comment.