Skip to content

Commit 0cd3070

Browse files
feat: document access key in swagger docs
issue: #218
1 parent 4e07a6b commit 0cd3070

File tree

5 files changed

+59
-11
lines changed

5 files changed

+59
-11
lines changed

lapis2/src/main/kotlin/org/genspectrum/lapis/LapisSpringConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import java.io.File
1919
@Configuration
2020
class LapisSpringConfig {
2121
@Bean
22-
fun openAPI(sequenceFilterFields: SequenceFilterFields) = buildOpenApiSchema(sequenceFilterFields)
22+
fun openAPI(sequenceFilterFields: SequenceFilterFields, databaseConfig: DatabaseConfig) =
23+
buildOpenApiSchema(sequenceFilterFields, databaseConfig)
2324

2425
@Bean
2526
fun databaseConfig(

lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@ package org.genspectrum.lapis
33
import io.swagger.v3.oas.models.Components
44
import io.swagger.v3.oas.models.OpenAPI
55
import io.swagger.v3.oas.models.media.Schema
6+
import org.genspectrum.lapis.config.DatabaseConfig
7+
import org.genspectrum.lapis.config.OpennessLevel
68
import org.genspectrum.lapis.config.SequenceFilterFields
79
import org.genspectrum.lapis.controller.MIN_PROPORTION_PROPERTY
810
import org.genspectrum.lapis.controller.REQUEST_SCHEMA
911
import org.genspectrum.lapis.controller.REQUEST_SCHEMA_WITH_MIN_PROPORTION
1012

11-
fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields): OpenAPI {
12-
val properties = sequenceFilterFields.fields
13+
fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields, databaseConfig: DatabaseConfig): OpenAPI {
14+
var properties = sequenceFilterFields.fields
1315
.map { (fieldName, fieldType) -> fieldName to Schema<String>().type(fieldType.openApiType) }
1416
.toMap()
1517

18+
if (databaseConfig.schema.opennessLevel == OpennessLevel.GISAID) {
19+
properties = properties + ("accessKey" to accessKeySchema)
20+
}
21+
1622
return OpenAPI()
1723
.components(
1824
Components().addSchemas(
@@ -30,3 +36,11 @@ fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields): OpenAPI {
3036
),
3137
)
3238
}
39+
40+
private val accessKeySchema = Schema<String>()
41+
.type("string")
42+
.description(
43+
"An access key that grants access to the protected data that this instance serves. " +
44+
"There are two types or access keys: One only grants access to aggregated data, " +
45+
"the other also grants access to detailed data.",
46+
)

lapis2/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,19 @@ private class ProtectedGisaidDataAuthorizationFilter(
9191
DataOpennessAuthorizationFilter(objectMapper) {
9292

9393
companion object {
94+
private val WHITELISTED_PATHS = listOf("/swagger-ui", "/api-docs")
9495
private val ENDPOINTS_THAT_SERVE_AGGREGATED_DATA = listOf("/aggregated", "/nucleotideMutations")
9596
}
9697

9798
override fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest): AuthorizationResult {
99+
if (WHITELISTED_PATHS.any { request.requestURI.startsWith(it) }) {
100+
return AuthorizationResult.success()
101+
}
102+
98103
val requestFields = getRequestFields(request)
99104

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

103108
if (accessKeys.fullAccessKey == accessKey) {
104109
return AuthorizationResult.success()
@@ -111,7 +116,7 @@ private class ProtectedGisaidDataAuthorizationFilter(
111116
return AuthorizationResult.success()
112117
}
113118

114-
return AuthorizationResult.failure("You are not authorized to access this endpoint.")
119+
return AuthorizationResult.failure("You are not authorized to access ${request.requestURI}.")
115120
}
116121

117122
private fun getRequestFields(request: CachedBodyHttpServletRequest): Map<String, String> {

lapis2/src/main/kotlin/org/genspectrum/lapis/controller/LapisController.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ class LapisController(private val siloQueryModel: SiloQueryModel, private val re
124124
description = "Bad Request",
125125
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
126126
),
127+
ApiResponse(
128+
responseCode = "403",
129+
description = "Forbidden",
130+
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
131+
),
127132
ApiResponse(
128133
responseCode = "500",
129134
description = "Internal Server Error",
@@ -149,6 +154,11 @@ private annotation class LapisAggregatedResponse
149154
description = "Bad Request",
150155
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
151156
),
157+
ApiResponse(
158+
responseCode = "403",
159+
description = "Forbidden",
160+
content = [Content(schema = Schema(implementation = LapisHttpErrorResponse::class))],
161+
),
152162
ApiResponse(
153163
responseCode = "500",
154164
description = "Internal Server Error",

lapis2/src/test/kotlin/org/genspectrum/lapis/auth/GisaidAuthorizationTest.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
4242
"""
4343
{
4444
"title": "Forbidden",
45-
"message": "An access key is required to access this endpoint."
45+
"message": "An access key is required to access /aggregated."
4646
}
4747
""",
4848
),
@@ -59,7 +59,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
5959
"""
6060
{
6161
"title": "Forbidden",
62-
"message": "An access key is required to access this endpoint."
62+
"message": "An access key is required to access /aggregated."
6363
}
6464
""",
6565
),
@@ -76,7 +76,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
7676
"""
7777
{
7878
"title": "Forbidden",
79-
"message": "You are not authorized to access this endpoint."
79+
"message": "You are not authorized to access /aggregated."
8080
}
8181
""",
8282
),
@@ -93,7 +93,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
9393
"""
9494
{
9595
"title": "Forbidden",
96-
"message": "You are not authorized to access this endpoint."
96+
"message": "You are not authorized to access /aggregated."
9797
}
9898
""",
9999
),
@@ -139,7 +139,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
139139
"""
140140
{
141141
"title": "Forbidden",
142-
"message": "You are not authorized to access this endpoint."
142+
"message": "You are not authorized to access /aggregated."
143143
}
144144
""",
145145
),
@@ -163,7 +163,7 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
163163
"""
164164
{
165165
"title": "Forbidden",
166-
"message": "You are not authorized to access this endpoint."
166+
"message": "You are not authorized to access /aggregated."
167167
}
168168
""",
169169
),
@@ -197,6 +197,24 @@ class GisaidAuthorizationTest(@Autowired val mockMvc: MockMvc) {
197197
verify { siloQueryModelMock.aggregate(mapOf("field1" to "value1")) }
198198
}
199199

200+
@Test
201+
fun `the swagger ui and api docs are always accessible`() {
202+
mockMvc.perform(
203+
MockMvcRequestBuilders.get("/swagger-ui/index.html"),
204+
)
205+
.andExpect(MockMvcResultMatchers.status().isOk)
206+
207+
mockMvc.perform(
208+
MockMvcRequestBuilders.get("/api-docs"),
209+
)
210+
.andExpect(MockMvcResultMatchers.status().isOk)
211+
212+
mockMvc.perform(
213+
MockMvcRequestBuilders.get("/api-docs.yaml"),
214+
)
215+
.andExpect(MockMvcResultMatchers.status().isOk)
216+
}
217+
200218
private fun postRequestWithBody(body: String) =
201219
MockMvcRequestBuilders.post(validRoute)
202220
.contentType(MediaType.APPLICATION_JSON)

0 commit comments

Comments
 (0)