diff --git a/.github/workflows/lapis.yml b/.github/workflows/lapis.yml
index 892542226..98414e974 100644
--- a/.github/workflows/lapis.yml
+++ b/.github/workflows/lapis.yml
@@ -141,7 +141,6 @@ jobs:
docker compose logs siloMultisegmented > e2e-logs/siloMultisegmented.log
docker compose logs siloPreprocessingMultisegmented > e2e-logs/siloPreprocessingMultisegmented.log
docker compose logs lapisOpen > e2e-logs/lapisOpen.log
- docker compose logs lapisProtected > e2e-logs/lapisProtected.log
docker compose logs lapisMultiSegmented > e2e-logs/lapisMultiSegmented.log
env:
SILO_TAG: 0.8.5
diff --git a/.idea/runConfigurations/LapisProtected.xml b/.idea/runConfigurations/LapisProtected.xml
deleted file mode 100644
index 8ba408814..000000000
--- a/.idea/runConfigurations/LapisProtected.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/lapis-docs/src/components/ConfigGenerator/configContext.tsx b/lapis-docs/src/components/ConfigGenerator/configContext.tsx
index 04f8ace6e..85a65714a 100644
--- a/lapis-docs/src/components/ConfigGenerator/configContext.tsx
+++ b/lapis-docs/src/components/ConfigGenerator/configContext.tsx
@@ -4,9 +4,8 @@ import { z } from 'zod';
export type ConfigType = 'SILO' | 'Pathoplexus';
export const LAPIS_OPENNESS_OPEN = 'OPEN';
-export const LAPIS_OPENNESS_PROTECTED = 'PROTECTED';
-export const opennessLevelSchema = z.enum([LAPIS_OPENNESS_OPEN, LAPIS_OPENNESS_PROTECTED]);
+export const opennessLevelSchema = z.enum([LAPIS_OPENNESS_OPEN]);
export type OpennessLevel = z.infer;
export const metadataTypeSchema = z.enum([
diff --git a/lapis-e2e/.prettierignore b/lapis-e2e/.prettierignore
index df9332181..161c841f2 100644
--- a/lapis-e2e/.prettierignore
+++ b/lapis-e2e/.prettierignore
@@ -1,3 +1,2 @@
/test/lapisClient
-/test/lapisClientProtected
/test/lapisClientMultiSegmented
\ No newline at end of file
diff --git a/lapis-e2e/generateOpenApiClients.sh b/lapis-e2e/generateOpenApiClients.sh
index 0d25648f5..7a58ee5bb 100755
--- a/lapis-e2e/generateOpenApiClients.sh
+++ b/lapis-e2e/generateOpenApiClients.sh
@@ -4,10 +4,8 @@ set -euo pipefail
cd "../lapis"
./gradlew generateOpenApiDocs
-./gradlew generateOpenApiDocs -PopennessLevel=protected
./gradlew generateOpenApiDocs -Psegmented=true
cd -
npm run generateLapisClient
-npm run generateLapisClientProtected
npm run generateLapisClientMultiSegmented
diff --git a/lapis-e2e/package.json b/lapis-e2e/package.json
index e559ff6a2..3f588d9c0 100644
--- a/lapis-e2e/package.json
+++ b/lapis-e2e/package.json
@@ -7,14 +7,10 @@
"generateLapisClient": "npm run runOpenApiGenerator && npm run copyGeneratedFiles && npm run cleanUpGeneratedFiles",
"runOpenApiGenerator": "openapi-generator-cli generate -i ../lapis/lapis-openapi-single-segmented.json -g typescript-fetch -o generated-sources",
"copyGeneratedFiles": "mkdir -p test/lapisClient && cp generated-sources/index.ts generated-sources/runtime.ts test/lapisClient && cp -r generated-sources/apis generated-sources/models test/lapisClient",
- "generateLapisClientProtected": "npm run runOpenApiGeneratorProtected && npm run copyGeneratedFilesProtected && npm run cleanUpGeneratedFilesProtected",
- "runOpenApiGeneratorProtected": "openapi-generator-cli generate -i ../lapis/lapis-openapi-single-segmented-protected.json -g typescript-fetch -o generated-sources-protected",
- "copyGeneratedFilesProtected": "mkdir -p test/lapisClientProtected && cp generated-sources-protected/index.ts generated-sources-protected/runtime.ts test/lapisClientProtected && cp -r generated-sources-protected/apis generated-sources-protected/models test/lapisClientProtected",
"generateLapisClientMultiSegmented": "npm run runOpenApiGeneratorMultiSegmented && npm run copyGeneratedFilesMultiSegmented && npm run cleanUpGeneratedFilesMultiSegmented",
"runOpenApiGeneratorMultiSegmented": "openapi-generator-cli generate -i ../lapis/lapis-openapi-multi-segmented.json -g typescript-fetch -o generated-sources-multi-segmented",
"copyGeneratedFilesMultiSegmented": "mkdir -p test/lapisClientMultiSegmented && cp generated-sources-multi-segmented/index.ts generated-sources-multi-segmented/runtime.ts test/lapisClientMultiSegmented && cp -r generated-sources-multi-segmented/apis generated-sources-multi-segmented/models test/lapisClientMultiSegmented",
"cleanUpGeneratedFiles": "rm -rf generated-sources",
- "cleanUpGeneratedFilesProtected": "rm -rf generated-sources-protected",
"cleanUpGeneratedFilesMultiSegmented": "rm -rf generated-sources-multi-segmented",
"check-format": "prettier --check .",
"format": "prettier --write ."
diff --git a/lapis-e2e/test/common.ts b/lapis-e2e/test/common.ts
index 5722ac766..be1243413 100644
--- a/lapis-e2e/test/common.ts
+++ b/lapis-e2e/test/common.ts
@@ -7,17 +7,13 @@ import {
SingleSegmentedSequenceControllerApi,
} from './lapisClient';
import { MutationsOverTimeControllerApi } from './lapisClient/index';
-import { LapisControllerApi as LapisControllerApiMultiSegmented } from './lapisClientMultiSegmented';
-import { expect } from 'chai';
-
import {
- LapisControllerApi as LapisControllerApiProtected,
- Configuration as ConfigurationProtected,
-} from './lapisClientProtected';
-import { MultiSegmentedSequenceControllerApi } from './lapisClientMultiSegmented';
+ LapisControllerApi as LapisControllerApiMultiSegmented,
+ MultiSegmentedSequenceControllerApi,
+} from './lapisClientMultiSegmented';
+import { expect } from 'chai';
export const basePath = 'http://localhost:8090';
-export const basePathProtected = 'http://localhost:8092';
export const basePathMultiSegmented = 'http://localhost:8094';
const middleware: Middleware = {
@@ -51,9 +47,6 @@ export const actuatorClient = new ActuatorApi(new Configuration({ basePath })).w
export const mutOverTimeClient = new MutationsOverTimeControllerApi(
new Configuration({ basePath })
).withMiddleware(middleware);
-export const lapisClientProtected = new LapisControllerApiProtected(
- new ConfigurationProtected({ basePath: basePathProtected })
-).withMiddleware(middleware);
export const lapisClientMultiSegmented = new LapisControllerApiMultiSegmented(
new Configuration({ basePath: basePathMultiSegmented })
).withMiddleware(middleware);
diff --git a/lapis-e2e/test/lapisClientProtected/.gitignore b/lapis-e2e/test/lapisClientProtected/.gitignore
deleted file mode 100644
index 72e8ffc0d..000000000
--- a/lapis-e2e/test/lapisClientProtected/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
diff --git a/lapis-e2e/test/protectedRoutes.spec.ts b/lapis-e2e/test/protectedRoutes.spec.ts
deleted file mode 100644
index 25480dc61..000000000
--- a/lapis-e2e/test/protectedRoutes.spec.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import crypto from 'crypto';
-import { expect } from 'chai';
-import { basePathProtected, lapisClientProtected } from './common';
-
-const aggregatedAccessKey = 'testAggregatedDataAccessKey';
-const fullAccessKey = 'testFullAccessKey';
-
-function hashAccessKey(baseKey: string): string {
- const epoch = Math.floor(Date.now() / 1000);
- return crypto.createHash('sha256').update(`${baseKey}:${epoch}`).digest('hex');
-}
-
-describe('Protected mode on GET requests', () => {
- it('should deny access, when no access key is provided', async () => {
- const result = await fetch(basePathProtected + '/sample/aggregated');
-
- expect(result.status).equals(403);
- expect(result.headers.get('Content-Type')).equals('application/json;charset=ISO-8859-1');
- expect((await result.json()).error).to.deep.equal({
- detail: 'An access key is required to access /sample/aggregated.',
- status: 403,
- title: 'Forbidden',
- type: 'about:blank',
- });
- });
-
- it('should deny access, when wrong access key is provided', async () => {
- const invalidAccessKey = 'invalidKey';
- const result = await fetch(`${basePathProtected}/sample/aggregated?accessKey=${invalidAccessKey}`);
-
- expect(result.status).equals(403);
- expect(result.headers.get('Content-Type')).equals('application/json;charset=ISO-8859-1');
- expect((await result.json()).error).to.deep.equal({
- detail: 'You are not authorized to access /sample/aggregated.',
- status: 403,
- title: 'Forbidden',
- type: 'about:blank',
- });
- });
-
- it('should deny access, when providing aggregated access key on non aggregated route', async () => {
- const result = await fetch(
- `${basePathProtected}/sample/details?accessKey=${hashAccessKey(aggregatedAccessKey)}`
- );
-
- expect(result.status).equals(403);
- expect(result.headers.get('Content-Type')).equals('application/json;charset=ISO-8859-1');
- expect((await result.json()).error).to.deep.equal({
- detail: 'You are not authorized to access /sample/details.',
- status: 403,
- title: 'Forbidden',
- type: 'about:blank',
- });
- });
-
- it('should grant access, when providing aggregated access key', async () => {
- const result = await fetch(
- `${basePathProtected}/sample/aggregated?accessKey=${hashAccessKey(aggregatedAccessKey)}`
- );
-
- expect(result.status).equals(200);
- });
-
- it('should grant access, when providing full access key', async () => {
- const result = await fetch(`${basePathProtected}/sample/aggregated?accessKey=${fullAccessKey}`);
-
- expect(result.status).equals(200);
- });
-
- it('should grant access, when providing full access key on non aggregated route', async () => {
- const result = await fetch(`${basePathProtected}/sample/details?accessKey=${fullAccessKey}`);
-
- expect(result.status).equals(200);
- });
-});
-
-describe('Protected mode on POST requests', () => {
- it('should deny access, when no access key is provided', async () => {
- const result = lapisClientProtected.postAggregated({ aggregatedPostRequest: {} });
-
- await expectResponseStatusIs(result, 403);
- });
-
- it('should deny access, when wrong access key is provided', async () => {
- const invalidAccessKey = 'invalidKey';
- const result = lapisClientProtected.postAggregated({
- aggregatedPostRequest: { accessKey: invalidAccessKey },
- });
-
- await expectResponseStatusIs(result, 403);
- });
-
- it('should deny access, when providing aggregated access key on non aggregated route', async () => {
- const result = lapisClientProtected.postDetails({
- detailsPostRequest: { accessKey: hashAccessKey(aggregatedAccessKey) },
- });
-
- await expectResponseStatusIs(result, 403);
- });
-
- it('should grant access, when providing aggregated access key', async () => {
- const result = lapisClientProtected.postAggregated({
- aggregatedPostRequest: { accessKey: hashAccessKey(aggregatedAccessKey) },
- });
-
- expect(await result).to.be.ok;
- });
-
- it('should grant access, when providing full access key', async () => {
- const result = lapisClientProtected.postAggregated({
- aggregatedPostRequest: { accessKey: fullAccessKey },
- });
-
- expect(await result).to.be.ok;
- });
-
- it('should grant access, when providing full access key on non aggregated route', async () => {
- const result = lapisClientProtected.postDetails({
- detailsPostRequest: { accessKey: fullAccessKey },
- });
-
- expect(await result).to.be.ok;
- });
-
- async function expectResponseStatusIs(response: Promise, expectedStatus: number) {
- try {
- const success = await response;
- expect.fail('Expected response to be rejected, but was ' + JSON.stringify(success));
- } catch (e) {
- expect(e).to.have.property('response').that.has.property('status', expectedStatus);
- }
- }
-});
diff --git a/lapis-e2e/testData/singleSegmented/protectedTestDatabaseConfig.yaml b/lapis-e2e/testData/singleSegmented/protectedTestDatabaseConfig.yaml
deleted file mode 100644
index 5ee5af904..000000000
--- a/lapis-e2e/testData/singleSegmented/protectedTestDatabaseConfig.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-schema:
- instanceName: sars_cov-2_minimal_test_config
- opennessLevel: PROTECTED
- metadata:
- - name: primaryKey
- type: string
- - name: usherTree
- type: string
- isPhyloTreeField: true
- - name: date
- type: date
- - name: region
- type: string
- generateIndex: true
- - name: country
- type: string
- generateIndex: true
- - name: pangoLineage
- type: string
- generateIndex: true
- generateLineageIndex: lineage_definitions
- - name: division
- type: string
- generateIndex: true
- - name: age
- type: int
- - name: qc_value
- type: float
- - name: test_boolean_column
- type: boolean
- features:
- - name: sarsCoV2VariantQuery
- primaryKey: primaryKey
diff --git a/lapis/.gitignore b/lapis/.gitignore
index fcddde85d..5422fbbea 100644
--- a/lapis/.gitignore
+++ b/lapis/.gitignore
@@ -4,9 +4,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
/lapis-openapi.json
-/lapis-openapi-protected.json
log/
logs/
/lapis-openapi-multi-segmented.json
/lapis-openapi-single-segmented.json
-/lapis-openapi-single-segmented-protected.json
diff --git a/lapis/README.md b/lapis/README.md
index 25742968b..f398131b3 100644
--- a/lapis/README.md
+++ b/lapis/README.md
@@ -135,9 +135,3 @@ To generate the OpenApi docs for an instance with multi-segmented reference geno
```bash
./gradlew generateOpenApiDocs -Psegmented=true
```
-
-To generate the OpenApi docs for a protected instance run
-```bash
-./gradlew generateOpenApiDocs -PopennessLevel=protected
-```
-
diff --git a/lapis/build.gradle b/lapis/build.gradle
index 6c9d0c4af..4a2c51ef8 100644
--- a/lapis/build.gradle
+++ b/lapis/build.gradle
@@ -90,24 +90,21 @@ ktlint {
openApi {
outputDir.set(file("$rootDir"))
- def opennessLevel = project.hasProperty("opennessLevel") ? project.opennessLevel : "open"
def segmented = project.hasProperty("segmented") ? project.segmented : "false"
- def getCustomOutputFileName = { opennessLevel_, segmented_ ->
+ def getCustomOutputFileName = { segmented_ ->
if (segmented_ == "true") {
return "lapis-openapi-multi-segmented.json"
} else {
- return opennessLevel_ == "open" ? "lapis-openapi-single-segmented.json" : "lapis-openapi-single-segmented-protected.json"
+ return "lapis-openapi-single-segmented.json"
}
}
- def getCustomLapisConfig = { opennessLevel_, segmented_ ->
+ def getCustomLapisConfig = { segmented_ ->
if (segmented_ == "true") {
return "$rootDir/../lapis-e2e/testData/multiSegmented/testDatabaseConfig.yaml"
} else {
- return opennessLevel_ == "open"
- ? "$rootDir/../lapis-e2e/testData/singleSegmented/testDatabaseConfig.yaml"
- : "$rootDir/../lapis-e2e/testData/singleSegmented/protectedTestDatabaseConfig.yaml"
+ return "$rootDir/../lapis-e2e/testData/singleSegmented/testDatabaseConfig.yaml"
}
}
@@ -119,8 +116,8 @@ openApi {
}
}
- def customOutputFileName = getCustomOutputFileName(opennessLevel, segmented)
- def customLapisConfig = getCustomLapisConfig(opennessLevel, segmented)
+ def customOutputFileName = getCustomOutputFileName(segmented)
+ def customLapisConfig = getCustomLapisConfig(segmented)
def referenceGenomeFilename = getReferenceGenomeFilename(segmented)
outputFileName.set(customOutputFileName)
@@ -131,7 +128,6 @@ openApi {
"--silo.url=does.not.matter.here",
"--lapis.databaseConfig.path=$customLapisConfig",
"--referenceGenomeFilename=$referenceGenomeFilename",
- "--lapis.accessKeys.path=$rootDir/src/test/resources/config/testAccessKeys.yaml"
])
}
}
diff --git a/lapis/docker-compose.yml b/lapis/docker-compose.yml
index 8556f2ac6..accf5afc1 100644
--- a/lapis/docker-compose.yml
+++ b/lapis/docker-compose.yml
@@ -74,22 +74,3 @@ services:
source: ../lapis-e2e/testData/multiSegmented/reference_genomes.json
target: /workspace/reference_genomes.json
read_only: true
-
- lapisProtected:
- image: ghcr.io/genspectrum/lapis:${LAPIS_TAG}
- platform: linux/amd64
- ports:
- - "8092:8080"
- command: --silo.url=http://silo:8081 --lapis.accessKeys.path=/workspace/access_keys.yaml
- volumes:
- - type: bind
- source: ../lapis-e2e/testData/singleSegmented/protectedTestDatabaseConfig.yaml
- target: /workspace/database_config.yaml
- read_only: true
- - type: bind
- source: ../lapis-e2e/testData/singleSegmented/reference_genomes.json
- target: /workspace/reference_genomes.json
- read_only: true
- - type: bind
- source: ./src/test/resources/config/testAccessKeys.yaml
- target: /workspace/access_keys.yaml
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
index 2f135171a..02be0f23c 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
@@ -4,23 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
-import org.genspectrum.lapis.config.AccessKeys
-import org.genspectrum.lapis.config.AccessKeysReader
import org.genspectrum.lapis.config.DatabaseConfig
import org.genspectrum.lapis.config.OpennessLevel
-import org.genspectrum.lapis.controller.AGGREGATED_ROUTE
-import org.genspectrum.lapis.controller.AMINO_ACID_INSERTIONS_ROUTE
-import org.genspectrum.lapis.controller.AMINO_ACID_MUTATIONS_OVER_TIME_ROUTE
-import org.genspectrum.lapis.controller.AMINO_ACID_MUTATIONS_ROUTE
-import org.genspectrum.lapis.controller.DATABASE_CONFIG_ROUTE
-import org.genspectrum.lapis.controller.INFO_ROUTE
-import org.genspectrum.lapis.controller.NUCLEOTIDE_INSERTIONS_ROUTE
-import org.genspectrum.lapis.controller.NUCLEOTIDE_MUTATIONS_OVER_TIME_ROUTE
-import org.genspectrum.lapis.controller.NUCLEOTIDE_MUTATIONS_ROUTE
-import org.genspectrum.lapis.controller.REFERENCE_GENOME_ROUTE
import org.genspectrum.lapis.controller.middleware.DATA_OPENNESS_AUTHORIZATION_FILTER_ORDER
-import org.genspectrum.lapis.request.ACCESS_KEY_PROPERTY
-import org.genspectrum.lapis.request.FIELDS_PROPERTY
import org.genspectrum.lapis.response.LapisErrorResponse
import org.genspectrum.lapis.response.LapisInfoFactory
import org.genspectrum.lapis.util.CachedBodyHttpServletRequest
@@ -30,29 +16,19 @@ import org.springframework.http.MediaType
import org.springframework.http.ProblemDetail
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
-import java.security.MessageDigest
-import java.time.Instant
@Component
class DataOpennessAuthorizationFilterFactory(
private val databaseConfig: DatabaseConfig,
private val objectMapper: ObjectMapper,
- private val accessKeysReader: AccessKeysReader,
private val lapisInfoFactory: LapisInfoFactory,
) {
- fun create() =
+ fun create(): DataOpennessAuthorizationFilter =
when (databaseConfig.schema.opennessLevel) {
OpennessLevel.OPEN -> AlwaysAuthorizedAuthorizationFilter(
objectMapper = objectMapper,
lapisInfoFactory = lapisInfoFactory,
)
-
- OpennessLevel.PROTECTED -> ProtectedDataAuthorizationFilter(
- objectMapper = objectMapper,
- lapisInfoFactory = lapisInfoFactory,
- accessKeys = accessKeysReader.read(),
- databaseConfig = databaseConfig,
- )
}
}
@@ -70,6 +46,7 @@ abstract class DataOpennessAuthorizationFilter(
when (val result = isAuthorizedForEndpoint(reReadableRequest)) {
AuthorizationResult.Success -> filterChain.doFilter(reReadableRequest, response)
+
is AuthorizationResult.Failure -> {
response.status = HttpStatus.FORBIDDEN.value()
response.contentType = MediaType.APPLICATION_JSON_VALUE
@@ -114,99 +91,3 @@ private class AlwaysAuthorizedAuthorizationFilter(
) : DataOpennessAuthorizationFilter(objectMapper, lapisInfoFactory) {
override fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest) = AuthorizationResult.success()
}
-
-private class ProtectedDataAuthorizationFilter(
- objectMapper: ObjectMapper,
- lapisInfoFactory: LapisInfoFactory,
- private val accessKeys: AccessKeys,
- private val databaseConfig: DatabaseConfig,
-) : DataOpennessAuthorizationFilter(objectMapper, lapisInfoFactory) {
- private val fieldsThatServeNonAggregatedData = databaseConfig.schema
- .metadata
- .filter { it.valuesAreUnique }
- .map { it.name }
-
- companion object {
- private val WHITELISTED_PATH_PREFIXES = listOf(
- "/swagger-ui",
- "/api-docs",
- "/actuator",
- "/sample$DATABASE_CONFIG_ROUTE",
- "/sample$REFERENCE_GENOME_ROUTE",
- )
- private val ENDPOINTS_THAT_SERVE_AGGREGATED_DATA = listOf(
- AGGREGATED_ROUTE,
- NUCLEOTIDE_MUTATIONS_ROUTE,
- AMINO_ACID_MUTATIONS_ROUTE,
- NUCLEOTIDE_INSERTIONS_ROUTE,
- AMINO_ACID_INSERTIONS_ROUTE,
- INFO_ROUTE,
- ).map { "/sample$it" } + listOf(
- NUCLEOTIDE_MUTATIONS_OVER_TIME_ROUTE,
- AMINO_ACID_MUTATIONS_OVER_TIME_ROUTE,
- ).map { "/component$it" }
- }
-
- override fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest): AuthorizationResult {
- val path = request.getProxyAwarePath()
-
- if (path == "/" || WHITELISTED_PATH_PREFIXES.any { path.startsWith(it) }) {
- return AuthorizationResult.success()
- }
-
- val accessKey = request.getStringField(ACCESS_KEY_PROPERTY)
- ?: return AuthorizationResult.failure("An access key is required to access $path.")
-
- if (accessKeys.fullAccessKeys.contains(accessKey)) {
- return AuthorizationResult.success()
- }
-
- if (getCurrentAccessKeys(accessKeys.aggregatedDataAccessKeys).contains(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) =
- fields.size == 1 && fields.first() == databaseConfig.schema.primaryKey
-
- private fun hash(text: String): String {
- val bytes = text.toByteArray()
- val md = MessageDigest.getInstance("SHA-256")
- val digest = md.digest(bytes)
- return digest.fold("", { str, it -> str + "%02x".format(it) })
- }
-
- private fun getCurrentAccessKeys(baseKey: String): Set {
- val current = Instant.now().epochSecond
- return listOf(-3, -2, -1, 0, 1, 2, 3)
- .map { current + it }
- .map { hash("$baseKey:$it") }
- .toSet()
- }
-
- private fun getCurrentAccessKeys(baseKeys: List): Set =
- baseKeys.flatMap {
- getCurrentAccessKeys(it)
- }.toSet()
-}
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/config/AccessKeys.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/config/AccessKeys.kt
deleted file mode 100644
index 6da03de02..000000000
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/config/AccessKeys.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.genspectrum.lapis.config
-
-import com.fasterxml.jackson.module.kotlin.readValue
-import org.genspectrum.lapis.util.YamlObjectMapper
-import org.springframework.beans.factory.annotation.Value
-import org.springframework.stereotype.Component
-import java.io.File
-
-@Component
-class AccessKeysReader(
- @param:Value("\${lapis.accessKeys.path:#{null}}") private val accessKeysFile: String?,
- private val yamlObjectMapper: YamlObjectMapper,
-) {
- fun read(): AccessKeys {
- if (accessKeysFile == null) {
- throw IllegalArgumentException("Cannot read LAPIS access keys, lapis.accessKeys.path was not set.")
- }
-
- return yamlObjectMapper.objectMapper.readValue(File(accessKeysFile))
- }
-}
-
-data class AccessKeys(
- val fullAccessKeys: List,
- val aggregatedDataAccessKeys: List,
-)
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/config/DatabaseConfig.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/config/DatabaseConfig.kt
index 2bd9a3933..f051780c5 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/config/DatabaseConfig.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/config/DatabaseConfig.kt
@@ -57,16 +57,4 @@ enum class OpennessLevel {
* The data served by this instance is fully open and may be shared with anyone.
*/
OPEN,
-
- /**
- * The data served by this instance must not be disclosed to everyone.
- *
- * Two access keys can be configured:
- *
- * One access key permits access to aggregated data. The aggregated data must be such that one cannot deduce
- * information about the data of the individual sequences.
- *
- * The other access key permits access to the full data.
- */
- PROTECTED,
}
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/model/mutationsOverTime/MutationsOverTimeModel.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/model/mutationsOverTime/MutationsOverTimeModel.kt
index 6b4b9e1d4..0cd6492ad 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/model/mutationsOverTime/MutationsOverTimeModel.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/model/mutationsOverTime/MutationsOverTimeModel.kt
@@ -213,8 +213,8 @@ class MutationsOverTimeModel(
val tasks = mutations.map { mutation ->
Callable {
- val counts = sendQuery(baseFilter, dateQuery, countQueryFn(mutation), dateField, false)
- val coverage = sendQuery(baseFilter, dateQuery, coverageQueryFn(mutation), dateField, false)
+ val counts = sendQuery(baseFilter, dateQuery, countQueryFn(mutation), dateField)
+ val coverage = sendQuery(baseFilter, dateQuery, coverageQueryFn(mutation), dateField)
listOf(counts.dataVersion, coverage.dataVersion) to
aggregateDailyMutationDataIntoDateRanges(
counts.queryResult,
@@ -267,7 +267,6 @@ class MutationsOverTimeModel(
dateQuery: SiloFilterExpression,
mutationQuery: SiloFilterExpression?,
dateField: String,
- checkProtection: Boolean = true,
): WithDataVersion> =
siloClient.sendQueryAndGetDataVersion(
SiloQuery(
@@ -286,7 +285,6 @@ class MutationsOverTimeModel(
),
),
setRequestDataVersion = false,
- checkProtection,
).map { it.toList() }
/**
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/AccessKeyParameterCustomizer.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/AccessKeyParameterCustomizer.kt
deleted file mode 100644
index a65793313..000000000
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/AccessKeyParameterCustomizer.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.genspectrum.lapis.openApi
-
-import io.swagger.v3.oas.models.OpenAPI
-import io.swagger.v3.oas.models.parameters.Parameter
-import org.genspectrum.lapis.config.DatabaseConfig
-import org.genspectrum.lapis.config.OpennessLevel
-import org.genspectrum.lapis.controller.INFO_ROUTE
-import org.genspectrum.lapis.request.ACCESS_KEY_PROPERTY
-import org.springdoc.core.customizers.OpenApiCustomizer
-import org.springframework.stereotype.Component
-
-@Component
-class AccessKeyParameterCustomizer(
- private val databaseConfig: DatabaseConfig,
-) : OpenApiCustomizer {
- companion object {
- private val PATH_WITH_ACCESS_KEY_PARAMETER = listOf(
- "/sample$INFO_ROUTE",
- )
- }
-
- override fun customise(openApi: OpenAPI) {
- if (databaseConfig.schema.opennessLevel == OpennessLevel.OPEN) {
- return
- }
-
- for ((_, path) in openApi.paths.filter { (url, _) ->
- PATH_WITH_ACCESS_KEY_PARAMETER.any {
- url.startsWith(it)
- }
- }) {
- path.get.addParametersItem(
- Parameter()
- .`in`("query")
- .name(ACCESS_KEY_PROPERTY)
- .description(ACCESS_KEY_DESCRIPTION)
- .schema(accessKeySchema()),
- )
- }
- }
-}
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt
index 0946338f6..383553e8f 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt
@@ -13,7 +13,6 @@ import org.genspectrum.lapis.config.DatabaseConfig
import org.genspectrum.lapis.config.DatabaseMetadata
import org.genspectrum.lapis.config.DatabaseSchema
import org.genspectrum.lapis.config.MetadataType
-import org.genspectrum.lapis.config.OpennessLevel
import org.genspectrum.lapis.config.ReferenceGenomeSchema
import org.genspectrum.lapis.config.ReferenceSequenceSchema
import org.genspectrum.lapis.config.SequenceFilterFieldName
@@ -36,7 +35,6 @@ import org.genspectrum.lapis.controller.middleware.Compression
import org.genspectrum.lapis.controller.middleware.DataFormat
import org.genspectrum.lapis.controller.middleware.SequencesDataFormat
import org.genspectrum.lapis.controller.middleware.TreeDataFormat
-import org.genspectrum.lapis.request.ACCESS_KEY_PROPERTY
import org.genspectrum.lapis.request.AMINO_ACID_INSERTIONS_PROPERTY
import org.genspectrum.lapis.request.AMINO_ACID_MUTATIONS_PROPERTY
import org.genspectrum.lapis.request.AminoAcidInsertion
@@ -78,7 +76,7 @@ fun buildOpenApiSchema(
Schema()
.types(setOf("object"))
.description("valid filters for sequence data")
- .properties(computePrimitiveFieldFilters(databaseConfig, sequenceFilterFields)),
+ .properties(computePrimitiveFieldFilters(sequenceFilterFields)),
)
.addSchemas(
REQUEST_SCHEMA_WITH_MIN_PROPORTION,
@@ -87,7 +85,6 @@ fun buildOpenApiSchema(
.description("valid filters for sequence data")
.properties(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = mutationsOrderByFieldsEnum(),
dataFormatSchema = dataFormatSchema(),
@@ -99,7 +96,6 @@ fun buildOpenApiSchema(
AGGREGATED_REQUEST_SCHEMA,
requestSchemaWithFields(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = aggregatedOrderByFieldsEnum(databaseConfig),
dataFormatSchema = dataFormatSchema(),
@@ -112,7 +108,6 @@ fun buildOpenApiSchema(
DETAILS_REQUEST_SCHEMA,
requestSchemaWithFields(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = detailsOrderByFieldsEnum(databaseConfig),
dataFormatSchema = dataFormatSchema(),
@@ -125,7 +120,6 @@ fun buildOpenApiSchema(
MOST_RECENT_COMMON_ANCESTOR_REQUEST_SCHEMA,
requestSchemaPhyloTree(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = detailsOrderByFieldsEnum(databaseConfig),
dataFormatSchema = dataFormatSchema(),
@@ -136,7 +130,6 @@ fun buildOpenApiSchema(
PHYLO_SUBTREE_REQUEST_SCHEMA,
requestSchemaPhyloTree(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = detailsOrderByFieldsEnum(databaseConfig),
dataFormatSchema = treeFormatSchema(),
@@ -147,7 +140,6 @@ fun buildOpenApiSchema(
INSERTIONS_REQUEST_SCHEMA,
requestSchemaForCommonSequenceFilters(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = insertionsOrderByFieldsEnum(),
dataFormatSchema = dataFormatSchema(),
@@ -158,7 +150,6 @@ fun buildOpenApiSchema(
ALIGNED_AMINO_ACID_SEQUENCE_REQUEST_SCHEMA,
aminoAcidSequencesRequestSchema(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = aminoAcidSequenceOrderByFieldsEnum(databaseConfig),
dataFormatSchema = sequencesFormatSchema(),
@@ -170,7 +161,6 @@ fun buildOpenApiSchema(
ALL_ALIGNED_AMINO_ACID_SEQUENCE_REQUEST_SCHEMA,
requestSchemaWithGenes(
requestProperties = getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = aminoAcidSequenceOrderByFieldsEnum(databaseConfig),
dataFormatSchema = sequencesFormatSchema(),
@@ -183,7 +173,6 @@ fun buildOpenApiSchema(
NUCLEOTIDE_SEQUENCE_REQUEST_SCHEMA,
nucleotideSequencesRequestSchema(
getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = nucleotideSequenceOrderByFieldsEnum(databaseConfig),
dataFormatSchema = sequencesFormatSchema(),
@@ -195,7 +184,6 @@ fun buildOpenApiSchema(
ALL_NUCLEOTIDE_SEQUENCE_REQUEST_SCHEMA,
requestSchemaWithSegment(
requestProperties = getSequenceFiltersWithFormat(
- databaseConfig = databaseConfig,
sequenceFilterFields = sequenceFilterFields,
orderByFieldsSchema = nucleotideSequenceOrderByFieldsEnum(databaseConfig),
dataFormatSchema = sequencesFormatSchema(),
@@ -206,7 +194,7 @@ fun buildOpenApiSchema(
)
.addSchemas(
MUTATIONS_OVER_TIME_REQUEST_SCHEMA,
- requestSchemaForMutationsOverTime(databaseConfig, sequenceFilterFields),
+ requestSchemaForMutationsOverTime(sequenceFilterFields),
)
.addSchemas(
AGGREGATED_RESPONSE_SCHEMA,
@@ -377,30 +365,27 @@ fun buildOpenApiSchema(
)
private fun getSequenceFiltersWithFormat(
- databaseConfig: DatabaseConfig,
sequenceFilterFields: SequenceFilterFields,
orderByFieldsSchema: Schema,
dataFormatSchema: Schema<*>,
): Map> =
- getSequenceFilters(databaseConfig, sequenceFilterFields, orderByFieldsSchema) +
+ getSequenceFilters(sequenceFilterFields, orderByFieldsSchema) +
Pair(FORMAT_PROPERTY, dataFormatSchema)
private fun getSequenceFilters(
- databaseConfig: DatabaseConfig,
sequenceFilterFields: SequenceFilterFields,
orderByFieldsSchema: Schema<*>,
): Map> =
- getBaseSequenceFilters(databaseConfig, sequenceFilterFields) +
+ getBaseSequenceFilters(sequenceFilterFields) +
Pair(ORDER_BY_PROPERTY, orderByPostSchema(orderByFieldsSchema)) +
Pair(LIMIT_PROPERTY, limitSchema()) +
Pair(OFFSET_PROPERTY, offsetSchema()) +
getCompressionAndDownloadSchema()
private fun getBaseSequenceFilters(
- databaseConfig: DatabaseConfig,
sequenceFilterFields: SequenceFilterFields,
): Map> =
- computePrimitiveFieldFilters(databaseConfig, sequenceFilterFields) +
+ computePrimitiveFieldFilters(sequenceFilterFields) +
Pair(NUCLEOTIDE_MUTATIONS_PROPERTY, nucleotideMutations()) +
Pair(AMINO_ACID_MUTATIONS_PROPERTY, aminoAcidMutations()) +
Pair(NUCLEOTIDE_INSERTIONS_PROPERTY, nucleotideInsertions()) +
@@ -428,15 +413,8 @@ fun compressionSchema(): Schema<*> =
._enum(Compression.entries.map { it.value })
private fun computePrimitiveFieldFilters(
- databaseConfig: DatabaseConfig,
sequenceFilterFields: SequenceFilterFields,
-): Map> =
- when (databaseConfig.schema.opennessLevel) {
- OpennessLevel.PROTECTED -> primitiveSequenceFilterFieldSchemas(sequenceFilterFields) +
- (ACCESS_KEY_PROPERTY to accessKeySchema())
-
- else -> primitiveSequenceFilterFieldSchemas(sequenceFilterFields)
- }
+): Map> = primitiveSequenceFilterFieldSchemas(sequenceFilterFields)
private fun lapisResponseSchema(dataSchema: Schema) =
Schema().types(setOf("object"))
@@ -664,10 +642,7 @@ private fun requestSchemaWithGenes(
Pair(FASTA_HEADER_TEMPLATE_PROPERTY, aminoAcidFastaHeaderTemplateSchema(databaseConfig)),
)
-private fun requestSchemaForMutationsOverTime(
- databaseConfig: DatabaseConfig,
- sequenceFilterFields: SequenceFilterFields,
-): Schema<*> =
+private fun requestSchemaForMutationsOverTime(sequenceFilterFields: SequenceFilterFields): Schema<*> =
Schema()
.types(setOf("object"))
.description("Request schema for fetching mutations over time.")
@@ -677,9 +652,7 @@ private fun requestSchemaForMutationsOverTime(
Schema()
.types(setOf("object"))
.description("Sequence filters")
- .properties(
- getBaseSequenceFilters(databaseConfig, sequenceFilterFields),
- ),
+ .properties(getBaseSequenceFilters(sequenceFilterFields)),
) +
getCompressionAndDownloadSchema() +
mapOf(
@@ -714,8 +687,6 @@ private fun getAggregatedResponseProperties(filterProperties: Map = StringSchema().description(ACCESS_KEY_DESCRIPTION)
-
private fun nucleotideMutationProportionSchema() =
mapOf(
"mutation" to StringSchema()
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/Schemas.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/Schemas.kt
index fe07d12ae..b62fad974 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/Schemas.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/openApi/Schemas.kt
@@ -136,13 +136,6 @@ Optionally set this to return the response compressed in the specified format.
Alternatively, you can set the '$ACCEPT_ENCODING' header to the respective value.
"""
-const val ACCESS_KEY_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.
-"""
-
const val SEGMENTS_DESCRIPTION =
"List of segments to retrieve sequences for. If not provided, all segments will be returned."
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/request/SpecialProperties.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/request/SpecialProperties.kt
index 5174b9d26..b72b99aad 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/request/SpecialProperties.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/request/SpecialProperties.kt
@@ -3,7 +3,6 @@ package org.genspectrum.lapis.request
import com.fasterxml.jackson.databind.node.JsonNodeType
const val FORMAT_PROPERTY = "dataFormat"
-const val ACCESS_KEY_PROPERTY = "accessKey"
const val MIN_PROPORTION_PROPERTY = "minProportion"
const val FIELDS_PROPERTY = "fields"
const val NUCLEOTIDE_MUTATIONS_PROPERTY = "nucleotideMutations"
@@ -24,7 +23,6 @@ const val PHYLO_TREE_FIELD_PROPERTY = "phyloTreeField"
val SPECIAL_REQUEST_PROPERTY_TYPES = mapOf(
FORMAT_PROPERTY to JsonNodeType.STRING,
- ACCESS_KEY_PROPERTY to JsonNodeType.STRING,
MIN_PROPORTION_PROPERTY to JsonNodeType.NUMBER,
FIELDS_PROPERTY to JsonNodeType.ARRAY,
NUCLEOTIDE_MUTATIONS_PROPERTY to JsonNodeType.ARRAY,
diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt
index f2077f6ce..5193e39b1 100644
--- a/lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt
+++ b/lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt
@@ -4,14 +4,11 @@ import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.genspectrum.lapis.config.DatabaseConfig
-import org.genspectrum.lapis.config.OpennessLevel
-import org.genspectrum.lapis.controller.BadRequestException
import org.genspectrum.lapis.controller.LapisHeaders.REQUEST_ID
import org.genspectrum.lapis.log
import org.genspectrum.lapis.logging.RequestContext
import org.genspectrum.lapis.logging.RequestIdContext
import org.genspectrum.lapis.response.InfoData
-import org.genspectrum.lapis.silo.SiloAction.AggregatedAction
import org.genspectrum.lapis.util.YamlObjectMapper
import org.springframework.cache.annotation.Cacheable
import org.springframework.http.HttpHeaders
@@ -43,11 +40,10 @@ class SiloClient(
fun sendQueryAndGetDataVersion(
query: SiloQuery,
setRequestDataVersion: Boolean = true,
- checkProtection: Boolean = true,
): WithDataVersion> {
val response = when (query.action.cacheable) {
- true -> cachedSiloClient.sendCachedQuery(query, checkProtection).map { it.stream() }
- else -> cachedSiloClient.sendQuery(query, checkProtection)
+ true -> cachedSiloClient.sendCachedQuery(query).map { it.stream() }
+ else -> cachedSiloClient.sendQuery(query)
}
if (setRequestDataVersion) {
@@ -104,35 +100,11 @@ open class CachedSiloClient(
"#query.action.randomize.class.simpleName == 'Disabled' || " +
"#query.action.randomize.class.simpleName == 'WithSeed')",
)
- open fun sendCachedQuery(
- query: SiloQuery,
- checkProtection: Boolean = true,
- ): WithDataVersion> =
- sendQuery(query, checkProtection)
+ open fun sendCachedQuery(query: SiloQuery): WithDataVersion> =
+ sendQuery(query)
.let { WithDataVersion(it.dataVersion, it.queryResult.toList()) }
- fun sendQuery(
- query: SiloQuery,
- checkProtection: Boolean = true,
- ): WithDataVersion> {
- // If the data is protected:
- // 1. If it is an aggregated request asking for more than 3 fields, throw an error.
- // 2. Check whether the number of sequences matching the filter set. If it is more than 0 but less than 10,
- // throw an error. (Exception: it is an aggregated request without any fields.)
- if (checkProtection && config.schema.opennessLevel == OpennessLevel.PROTECTED) {
- if (query.action is AggregatedAction && query.action.groupByFields.size > 3) {
- throw BadRequestException("Unauthorized")
- }
- if (!(query.action is AggregatedAction && query.action.groupByFields.isEmpty())) {
- val countQuery = SiloQuery(SiloAction.aggregated(), query.filterExpression)
- val countResponseStream = sendQuery(countQuery, false)
- val countResponse = countResponseStream.queryResult.toList().first()
- if (countResponse.count in 1..<10) {
- throw BadRequestException("Unauthorized")
- }
- }
- }
-
+ fun sendQuery(query: SiloQuery): WithDataVersion> {
if (RequestContextHolder.getRequestAttributes() != null) {
requestContext.cached = false
}
diff --git a/lapis/src/main/resources/application-docker.properties b/lapis/src/main/resources/application-docker.properties
index c3e19fc43..395b53cc7 100644
--- a/lapis/src/main/resources/application-docker.properties
+++ b/lapis/src/main/resources/application-docker.properties
@@ -1,2 +1 @@
lapis.databaseConfig.path=./database_config.yaml
-lapis.accessKeys.path=./access_keys.yaml
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt
deleted file mode 100644
index 437d2ad5b..000000000
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt
+++ /dev/null
@@ -1,367 +0,0 @@
-package org.genspectrum.lapis.auth
-
-import com.ninjasquad.springmockk.MockkBean
-import io.mockk.MockKAnnotations
-import io.mockk.every
-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
-import org.genspectrum.lapis.model.SiloQueryModel
-import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
-import org.genspectrum.lapis.response.LapisInfo
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.ValueSource
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.MediaType
-import org.springframework.test.web.servlet.MockMvc
-import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
-import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
-import java.security.MessageDigest
-import java.time.Instant
-import java.util.stream.Stream
-
-private const val NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR = """
-{
- "error" : {
- "title": "Forbidden",
- "detail": "You are not authorized to access /sample/aggregated."
- }
-}
-"""
-
-private const val FORBIDDEN_TO_ACCESS_ENDPOINT_ERROR = """
-{
- "error" : {
- "title": "Forbidden",
- "detail": "An access key is required to access /sample/aggregated."
- }
-}
-"""
-
-@SpringBootTest(properties = ["lapis.databaseConfig.path=src/test/resources/config/protectedDataDatabaseConfig.yaml"])
-@AutoConfigureMockMvc
-class ProtectedDataAuthorizationTest(
- @param:Autowired val mockMvc: MockMvc,
-) {
- @MockkBean
- lateinit var siloQueryModelMock: SiloQueryModel
-
- private val validRoute = AGGREGATED_ROUTE
-
- @MockkBean
- lateinit var lapisInfo: LapisInfo
-
- @BeforeEach
- fun setUp() {
- every { siloQueryModelMock.getAggregated(any()) } returns Stream.empty()
-
- every {
- lapisInfo.dataVersion
- } returns "1234"
-
- MockKAnnotations.init(this)
- }
-
- @Test
- fun `given no access key in GET request to protected instance, then access is denied`() {
- mockMvc.perform(getSample(validRoute))
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(FORBIDDEN_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given no access key in POST request to protected instance, then access is denied`() {
- mockMvc.perform(postRequestWithBody(""))
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(FORBIDDEN_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given wrong access key in GET request to protected instance, then access is denied`() {
- mockMvc.perform(getSample("$validRoute?accessKey=invalidKey"))
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given too old access key in GET request to protected instance, then access is denied`() {
- val oldKey = getCurrentAccessKey("testAggregatedDataAccessKey", Instant.now().epochSecond - 10)
- mockMvc.perform(getSample("$validRoute?accessKey=$oldKey"))
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given wrong access key in POST request to protected instance, then access is denied`() {
- mockMvc.perform(postRequestWithBody("""{"accessKey": "invalidKey"}"""))
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given valid access key for aggregated data in GET request to protected instance, then access is granted`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&field1=value1"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given 2s old access key for aggregated data in GET request to protected instance, then access is granted`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey", Instant.now().epochSecond - 2)
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&field1=value1"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given second valid access key for agg data in GET request to protected instance, then access is granted`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey2")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&field1=value1"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given valid access key for aggregated data in POST request to protected instance, then access is granted`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- postRequestWithBody(
- """ {
- "accessKey": "$currentKey",
- "field1": "value1"
- }""",
- ),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given aggregated access key in GET request but filters are too fine-grained, then access is denied`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&$PRIMARY_KEY_FIELD=value"),
- )
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `given aggregated access key in POST request but filters are too fine-grained, then access is denied`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- postRequestWithBody(
- """ {
- "accessKey": "$currentKey",
- "$PRIMARY_KEY_FIELD": "some value"
- }""",
- ),
- )
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @ParameterizedTest
- @ValueSource(
- strings = [
- "fields=$PRIMARY_KEY_FIELD,country",
- "fields=$PRIMARY_KEY_FIELD&fields=country",
- ],
- )
- fun `GIVEN aggregated access key in GET request but request stratifies too fine-grained THEN access is denied`(
- fieldsQueryParameter: String,
- ) {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&$fieldsQueryParameter"),
- )
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().json(NOT_AUTHORIZED_TO_ACCESS_ENDPOINT_ERROR))
- }
-
- @Test
- fun `GIVEN aggregated access key in GET request that stratifies non-fine-grained THEN access is granted`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&fields=country"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- }
-
- @Test
- fun `GIVEN aggregated access key in POST request but request stratifies too fine-grained THEN access is denied`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- postRequestWithBody(
- """ {
- "accessKey": "$currentKey",
- "fields": ["$PRIMARY_KEY_FIELD", "country"]
- }""",
- ),
- )
- .andExpect(status().isForbidden)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .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`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- getSample("$validRoute?accessKey=$currentKey&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`() {
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- postRequestWithBody(
- """ {
- "accessKey": "$currentKey",
- "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 Stream.empty()
-
- val currentKey = getCurrentAccessKey("testAggregatedDataAccessKey")
- mockMvc.perform(
- postSample(DETAILS_ROUTE)
- .contentType(MediaType.APPLICATION_JSON)
- .content(
- """{
- "accessKey": "$currentKey",
- "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(
- getSample("$validRoute?accessKey=testFullAccessKey&field1=value1"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given second valid access key for full access in GET request to protected instance, then access is granted`() {
- mockMvc.perform(
- getSample("$validRoute?accessKey=testFullAccessKey2&field1=value1"),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- @Test
- fun `given valid access key for full access in POST request to protected instance, then access is granted`() {
- mockMvc.perform(
- postRequestWithBody(
- """ {
- "accessKey": "testFullAccessKey",
- "field1": "value1"
- }""",
- ),
- )
- .andExpect(status().isOk)
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
-
- verify { siloQueryModelMock.getAggregated(sequenceFilterRequest()) }
- }
-
- private fun sequenceFilterRequest() =
- SequenceFiltersRequestWithFields(
- mapOf("field1" to listOf("value1")),
- emptyList(),
- emptyList(),
- emptyList(),
- emptyList(),
- emptyList(),
- )
-
- @Test
- fun `whitelisted routes are always accessible`() {
- mockMvc.perform(get("/swagger-ui/index.html"))
- .andExpect(status().isOk)
-
- mockMvc.perform(get("/api-docs"))
- .andExpect(status().isOk)
-
- mockMvc.perform(get("/api-docs.yaml"))
- .andExpect(status().isOk)
-
- mockMvc.perform(getSample(DATABASE_CONFIG_ROUTE))
- .andExpect(status().isOk)
-
- mockMvc.perform(getSample(REFERENCE_GENOME_ROUTE))
- .andExpect(status().isOk)
- }
-
- private fun postRequestWithBody(body: String) =
- postSample(validRoute)
- .contentType(MediaType.APPLICATION_JSON)
- .content(body)
-
- private fun hash(text: String): String {
- val bytes = text.toByteArray()
- val md = MessageDigest.getInstance("SHA-256")
- val digest = md.digest(bytes)
- return digest.fold("", { str, it -> str + "%02x".format(it) })
- }
-
- private fun getCurrentAccessKey(
- baseKey: String,
- epoch: Long = Instant.now().epochSecond,
- ): String = hash("$baseKey:$epoch")
-}
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/config/AccessKeysReaderTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/config/AccessKeysReaderTest.kt
deleted file mode 100644
index 2c5765ec3..000000000
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/config/AccessKeysReaderTest.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.genspectrum.lapis.config
-
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ActiveProfiles
-
-@SpringBootTest
-class AccessKeysReaderTest {
- @Autowired
- lateinit var underTest: AccessKeysReader
-
- @Test
- fun `given access keys file path as property then should successfully read access keys`() {
- val result = underTest.read()
-
- assertThat(
- result.fullAccessKeys,
- contains(
- "testFullAccessKey",
- "testFullAccessKey2",
- ),
- )
-
- assertThat(
- result.aggregatedDataAccessKeys,
- contains(
- "testAggregatedDataAccessKey",
- "testAggregatedDataAccessKey2",
- ),
- )
- }
-}
-
-@SpringBootTest
-@ActiveProfiles("testWithoutAccessKeys")
-class AccessKeysReaderWithPathNotSetTest {
- @Autowired
- lateinit var underTest: AccessKeysReader
-
- @Test
- fun `given access keys file path property is not set then should throw exception when reading access keys`() {
- assertThrows { underTest.read() }
- }
-}
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/AminoAcidMutationsOverTimeModelTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/AminoAcidMutationsOverTimeModelTest.kt
index 15638f3c3..f889d23af 100644
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/AminoAcidMutationsOverTimeModelTest.kt
+++ b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/AminoAcidMutationsOverTimeModelTest.kt
@@ -231,7 +231,7 @@ class AminoAcidMutationsOverTimeModelTest {
fun `given one data version change, then it succeeds`() {
var callCount = 0
every {
- siloQueryClient.sendQueryAndGetDataVersion(any(), false, any())
+ siloQueryClient.sendQueryAndGetDataVersion(any(), false)
} answers {
val version = if (callCount++ == 0) "1" else "2"
WithDataVersion(
@@ -259,7 +259,7 @@ class AminoAcidMutationsOverTimeModelTest {
fun `given more than once data version change, then it throws`() {
var callCount = 0
every {
- siloQueryClient.sendQueryAndGetDataVersion(any(), false, any())
+ siloQueryClient.sendQueryAndGetDataVersion(any(), false)
} answers {
val version = (callCount++).toString()
WithDataVersion(
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/Helpers.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/Helpers.kt
index c39908f3a..4b4eebc5c 100644
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/Helpers.kt
+++ b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/Helpers.kt
@@ -79,7 +79,6 @@ fun mockSiloCountQuery(
query.filterExpression.children.contains(dateBetweenFilter)
},
false,
- false,
)
} answers {
WithDataVersion(DUMMY_DATA_VERSION, queryResult)
@@ -141,7 +140,6 @@ private fun mockSiloCoverageQuery(
query.filterExpression.children.contains(dateBetween)
},
false,
- false,
)
} answers {
WithDataVersion(DUMMY_DATA_VERSION, queryResult)
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/NucleotideMutationsOverTimeModelTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/NucleotideMutationsOverTimeModelTest.kt
index b8a5e8627..b5db4b5be 100644
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/NucleotideMutationsOverTimeModelTest.kt
+++ b/lapis/src/test/kotlin/org/genspectrum/lapis/model/mutationsOverTime/NucleotideMutationsOverTimeModelTest.kt
@@ -234,7 +234,7 @@ class NucleotideMutationsOverTimeModelTest {
fun `given one data version change, then it succeeds`() {
var callCount = 0
every {
- siloQueryClient.sendQueryAndGetDataVersion(any(), false, any())
+ siloQueryClient.sendQueryAndGetDataVersion(any(), false)
} answers {
val version = if (callCount++ == 0) "1" else "2"
WithDataVersion(
@@ -262,7 +262,7 @@ class NucleotideMutationsOverTimeModelTest {
fun `given more than once data version change, then it throws`() {
var callCount = 0
every {
- siloQueryClient.sendQueryAndGetDataVersion(any(), false, any())
+ siloQueryClient.sendQueryAndGetDataVersion(any(), false)
} answers {
val version = (callCount++).toString()
WithDataVersion(
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/request/MutationProportionsRequestTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/request/MutationProportionsRequestTest.kt
index 73aa240bb..bd0c01cdb 100644
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/request/MutationProportionsRequestTest.kt
+++ b/lapis/src/test/kotlin/org/genspectrum/lapis/request/MutationProportionsRequestTest.kt
@@ -170,22 +170,6 @@ class MutationProportionsRequestTest {
minProportion = 0.7,
),
),
- Arguments.of(
- """
- {
- "accessKey": "some access key"
- }
- """,
- MutationProportionsRequest(
- sequenceFilters = emptyMap(),
- nucleotideMutations = emptyList(),
- aminoAcidMutations = emptyList(),
- nucleotideInsertions = emptyList(),
- aminoAcidInsertions = emptyList(),
- fields = emptyList(),
- minProportion = DEFAULT_MIN_PROPORTION,
- ),
- ),
Arguments.of(
"{}",
MutationProportionsRequest(
diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequestWithFieldsTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequestWithFieldsTest.kt
index 407250117..6fedad1bf 100644
--- a/lapis/src/test/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequestWithFieldsTest.kt
+++ b/lapis/src/test/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequestWithFieldsTest.kt
@@ -194,21 +194,6 @@ class SequenceFiltersRequestWithFieldsTest {
emptyList(),
),
),
- Arguments.of(
- """
- {
- "accessKey": "some access key"
- }
- """,
- SequenceFiltersRequestWithFields(
- emptyMap(),
- emptyList(),
- emptyList(),
- emptyList(),
- emptyList(),
- emptyList(),
- ),
- ),
Arguments.of(
"{}",
SequenceFiltersRequestWithFields(
diff --git a/lapis/src/test/resources/application-test.properties b/lapis/src/test/resources/application-test.properties
index 9a81c248e..f35aa88a0 100644
--- a/lapis/src/test/resources/application-test.properties
+++ b/lapis/src/test/resources/application-test.properties
@@ -1,6 +1,5 @@
silo.url=http://url.to.silo
lapis.databaseConfig.path=src/test/resources/config/testDatabaseConfig.yaml
-lapis.accessKeys.path=src/test/resources/config/testAccessKeys.yaml
referenceGenome.segments=main,other_segment
referenceGenome.genes=gene1,gene2
referenceGenomeFilename=src/test/resources/config/reference-genomes.json
diff --git a/lapis/src/test/resources/config/protectedDataDatabaseConfig.yaml b/lapis/src/test/resources/config/protectedDataDatabaseConfig.yaml
deleted file mode 100644
index 3851e7e3b..000000000
--- a/lapis/src/test/resources/config/protectedDataDatabaseConfig.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-schema:
- instanceName: protectedDataTestConfig
- opennessLevel: PROTECTED
- metadata:
- - name: primaryKey
- type: string
- valuesAreUnique: true
- - name: country
- type: string
- primaryKey: primaryKey
diff --git a/lapis/src/test/resources/config/testAccessKeys.yaml b/lapis/src/test/resources/config/testAccessKeys.yaml
deleted file mode 100644
index 68aea6603..000000000
--- a/lapis/src/test/resources/config/testAccessKeys.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-fullAccessKeys:
- - testFullAccessKey
- - testFullAccessKey2
-aggregatedDataAccessKeys:
- - testAggregatedDataAccessKey
- - testAggregatedDataAccessKey2