Skip to content

Commit

Permalink
feat: document access key in swagger docs
Browse files Browse the repository at this point in the history
issue: #218
  • Loading branch information
fengelniederhammer committed May 2, 2023
1 parent 4e07a6b commit 0cd3070
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import java.io.File
@Configuration
class LapisSpringConfig {
@Bean
fun openAPI(sequenceFilterFields: SequenceFilterFields) = buildOpenApiSchema(sequenceFilterFields)
fun openAPI(sequenceFilterFields: SequenceFilterFields, databaseConfig: DatabaseConfig) =
buildOpenApiSchema(sequenceFilterFields, databaseConfig)

@Bean
fun databaseConfig(
Expand Down
18 changes: 16 additions & 2 deletions lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ package org.genspectrum.lapis
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.media.Schema
import org.genspectrum.lapis.config.DatabaseConfig
import org.genspectrum.lapis.config.OpennessLevel
import org.genspectrum.lapis.config.SequenceFilterFields
import org.genspectrum.lapis.controller.MIN_PROPORTION_PROPERTY
import org.genspectrum.lapis.controller.REQUEST_SCHEMA
import org.genspectrum.lapis.controller.REQUEST_SCHEMA_WITH_MIN_PROPORTION

fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields): OpenAPI {
val properties = sequenceFilterFields.fields
fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields, databaseConfig: DatabaseConfig): OpenAPI {
var properties = sequenceFilterFields.fields
.map { (fieldName, fieldType) -> fieldName to Schema<String>().type(fieldType.openApiType) }
.toMap()

if (databaseConfig.schema.opennessLevel == OpennessLevel.GISAID) {
properties = properties + ("accessKey" to accessKeySchema)
}

return OpenAPI()
.components(
Components().addSchemas(
Expand All @@ -30,3 +36,11 @@ fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields): OpenAPI {
),
)
}

private val accessKeySchema = Schema<String>()
.type("string")
.description(
"An access key that grants access to the protected data that this instance serves. " +
"There are two types or access keys: One only grants access to aggregated data, " +
"the other also grants access to detailed data.",
)
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,19 @@ private class ProtectedGisaidDataAuthorizationFilter(
DataOpennessAuthorizationFilter(objectMapper) {

companion object {
private val WHITELISTED_PATHS = listOf("/swagger-ui", "/api-docs")
private val ENDPOINTS_THAT_SERVE_AGGREGATED_DATA = listOf("/aggregated", "/nucleotideMutations")
}

override fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest): AuthorizationResult {
if (WHITELISTED_PATHS.any { request.requestURI.startsWith(it) }) {
return AuthorizationResult.success()
}

val requestFields = getRequestFields(request)

val accessKey = requestFields[ACCESS_KEY_PROPERTY]
?: return AuthorizationResult.failure("An access key is required to access this endpoint.")
?: return AuthorizationResult.failure("An access key is required to access ${request.requestURI}.")

if (accessKeys.fullAccessKey == accessKey) {
return AuthorizationResult.success()
Expand All @@ -111,7 +116,7 @@ private class ProtectedGisaidDataAuthorizationFilter(
return AuthorizationResult.success()
}

return AuthorizationResult.failure("You are not authorized to access this endpoint.")
return AuthorizationResult.failure("You are not authorized to access ${request.requestURI}.")
}

private fun getRequestFields(request: CachedBodyHttpServletRequest): Map<String, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ class LapisController(private val siloQueryModel: SiloQueryModel, private val re
description = "Bad Request",
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
),
ApiResponse(
responseCode = "403",
description = "Forbidden",
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
),
ApiResponse(
responseCode = "500",
description = "Internal Server Error",
Expand All @@ -149,6 +154,11 @@ private annotation class LapisAggregatedResponse
description = "Bad Request",
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
),
ApiResponse(
responseCode = "403",
description = "Forbidden",
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
),
ApiResponse(
responseCode = "500",
description = "Internal Server Error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "An access key is required to access this endpoint."
"message": "An access key is required to access /aggregated."
}
""",
),
Expand All @@ -59,7 +59,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "An access key is required to access this endpoint."
"message": "An access key is required to access /aggregated."
}
""",
),
Expand All @@ -76,7 +76,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "You are not authorized to access this endpoint."
"message": "You are not authorized to access /aggregated."
}
""",
),
Expand All @@ -93,7 +93,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "You are not authorized to access this endpoint."
"message": "You are not authorized to access /aggregated."
}
""",
),
Expand Down Expand Up @@ -139,7 +139,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "You are not authorized to access this endpoint."
"message": "You are not authorized to access /aggregated."
}
""",
),
Expand All @@ -163,7 +163,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
"""
{
"title": "Forbidden",
"message": "You are not authorized to access this endpoint."
"message": "You are not authorized to access /aggregated."
}
""",
),
Expand Down Expand Up @@ -197,6 +197,24 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
verify { siloQueryModelMock.aggregate(mapOf("field1" to "value1")) }
}

@Test
fun `the swagger ui and api docs are always accessible`() {
mockMvc.perform(
MockMvcRequestBuilders.get("/swagger-ui/index.html"),
)
.andExpect(MockMvcResultMatchers.status().isOk)

mockMvc.perform(
MockMvcRequestBuilders.get("/api-docs"),
)
.andExpect(MockMvcResultMatchers.status().isOk)

mockMvc.perform(
MockMvcRequestBuilders.get("/api-docs.yaml"),
)
.andExpect(MockMvcResultMatchers.status().isOk)
}

private fun postRequestWithBody(body: String) =
MockMvcRequestBuilders.post(validRoute)
.contentType(MediaType.APPLICATION_JSON)
Expand Down

0 comments on commit 0cd3070

Please sign in to comment.