Skip to content

Commit

Permalink
feat: allow that fields only contains the primary key in protected …
Browse files Browse the repository at this point in the history
…mode #623

The result will not be of much use, since you only get all primary keys, but no additional information. This is required for the UShER integration of cov-spectrum.
  • Loading branch information
fengelniederhammer committed Feb 2, 2024
1 parent eaa832c commit 0ac0709
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .idea/runConfigurations/LapisV2Protected.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class DataOpennessAuthorizationFilterFactory(
OpennessLevel.PROTECTED -> ProtectedDataAuthorizationFilter(
objectMapper,
accessKeysReader.read(),
databaseConfig.schema.metadata.filter { it.valuesAreUnique }.map { it.name },
databaseConfig,
)
}
}
Expand Down Expand Up @@ -96,9 +96,14 @@ private class AlwaysAuthorizedAuthorizationFilter(objectMapper: ObjectMapper) :
private class ProtectedDataAuthorizationFilter(
objectMapper: ObjectMapper,
private val accessKeys: AccessKeys,
private val fieldsThatServeNonAggregatedData: List<String>,
private val databaseConfig: DatabaseConfig,
) :
DataOpennessAuthorizationFilter(objectMapper) {
private val fieldsThatServeNonAggregatedData = databaseConfig.schema
.metadata
.filter { it.valuesAreUnique }
.map { it.name }

companion object {
private val WHITELISTED_PATH_PREFIXES = listOf(
"/swagger-ui",
Expand Down Expand Up @@ -130,15 +135,30 @@ private class ProtectedDataAuthorizationFilter(
return AuthorizationResult.success()
}

val endpointServesAggregatedData = ENDPOINTS_THAT_SERVE_AGGREGATED_DATA.contains(path) &&
fieldsThatServeNonAggregatedData.intersect(request.getRequestFieldNames()).isEmpty() &&
request.getStringArrayField(FIELDS_PROPERTY).intersect(fieldsThatServeNonAggregatedData.toSet())
.isEmpty()

if (endpointServesAggregatedData && accessKeys.aggregatedDataAccessKey == accessKey) {
if (accessKeys.aggregatedDataAccessKey == accessKey && endpointServesAggregatedData(request)) {
return AuthorizationResult.success()
}

return AuthorizationResult.failure("You are not authorized to access $path.")
}

private fun endpointServesAggregatedData(request: CachedBodyHttpServletRequest): Boolean {
val fields = request.getStringArrayField(FIELDS_PROPERTY)
if (containsOnlyPrimaryKey(fields)) {
return true
}

if (!ENDPOINTS_THAT_SERVE_AGGREGATED_DATA.contains(request.getProxyAwarePath())) {
return false
}

if (fieldsThatServeNonAggregatedData.intersect(request.getRequestFieldNames()).isNotEmpty()) {
return false
}

return fields.intersect(fieldsThatServeNonAggregatedData.toSet()).isEmpty()
}

private fun containsOnlyPrimaryKey(fields: List<String>) =
fields.size == 1 && fields.first() == databaseConfig.schema.primaryKey
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.mockk.verify
import org.genspectrum.lapis.PRIMARY_KEY_FIELD
import org.genspectrum.lapis.controller.AGGREGATED_ROUTE
import org.genspectrum.lapis.controller.DATABASE_CONFIG_ROUTE
import org.genspectrum.lapis.controller.DETAILS_ROUTE
import org.genspectrum.lapis.controller.REFERENCE_GENOME_ROUTE
import org.genspectrum.lapis.controller.getSample
import org.genspectrum.lapis.controller.postSample
Expand Down Expand Up @@ -155,7 +156,6 @@ class ProtectedDataAuthorizationTest(
@ParameterizedTest
@ValueSource(
strings = [
"fields=$PRIMARY_KEY_FIELD",
"fields=$PRIMARY_KEY_FIELD,country",
"fields=$PRIMARY_KEY_FIELD&fields=country",
],
Expand All @@ -180,21 +180,13 @@ class ProtectedDataAuthorizationTest(
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
}

@ParameterizedTest
@ValueSource(
strings = [
"""["$PRIMARY_KEY_FIELD"]""",
"""["$PRIMARY_KEY_FIELD", "country"]""",
],
)
fun `GIVEN aggregated access key in POST request but request stratifies too fine-grained THEN access is denied`(
fieldsJson: String,
) {
@Test
fun `GIVEN aggregated access key in POST request but request stratifies too fine-grained THEN access is denied`() {
mockMvc.perform(
postRequestWithBody(
""" {
"accessKey": "testAggregatedDataAccessKey",
"fields": $fieldsJson
"fields": ["$PRIMARY_KEY_FIELD", "country"]
}""",
),
)
Expand All @@ -203,6 +195,47 @@ class ProtectedDataAuthorizationTest(
.andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
}

@Test
fun `GIVEN aggregated access key in GET request where fields only contains primary key THEN access is granted`() {
mockMvc.perform(
getSample("$validRoute?accessKey=testAggregatedDataAccessKey&fields=$PRIMARY_KEY_FIELD"),
)
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
}

@Test
fun `GIVEN aggregated access key in POST request where fields only contains primary key THEN access is granted`() {
mockMvc.perform(
postRequestWithBody(
""" {
"accessKey": "testAggregatedDataAccessKey",
"fields": ["$PRIMARY_KEY_FIELD"]
}""",
),
)
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
}

@Test
fun `GIVEN aggregated accessKey in details request where fields only contains primaryKey THEN access is granted`() {
every { siloQueryModelMock.getDetails(any()) } returns emptyList()

mockMvc.perform(
postSample(DETAILS_ROUTE)
.contentType(MediaType.APPLICATION_JSON)
.content(
"""{
"accessKey": "testAggregatedDataAccessKey",
"fields": ["$PRIMARY_KEY_FIELD"]
}""",
),
)
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
}

@Test
fun `given valid access key for full access in GET request to protected instance, then access is granted`() {
mockMvc.perform(
Expand Down
32 changes: 32 additions & 0 deletions siloLapisTests/testData/protectedTestDatabaseConfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
schema:
instanceName: sars_cov-2_minimal_test_config
opennessLevel: PROTECTED
metadata:
- name: primaryKey
type: string
- name: date
type: date
- name: region
type: string
generateIndex: true
- name: country
type: string
generateIndex: true
- name: pangoLineage
type: pango_lineage
- name: division
type: string
generateIndex: true
- name: age
type: int
- name: qc_value
type: float
- name: insertions
type: insertion
- name: aaInsertions
type: aaInsertion
features:
- name: sarsCoV2VariantQuery
primaryKey: primaryKey
dateToSortBy: date
partitionBy: pangoLineage

0 comments on commit 0ac0709

Please sign in to comment.