From c18617213dc5bc10138e06d2a0577aa8d9f02eba Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Tue, 9 Nov 2021 09:41:31 +0100 Subject: [PATCH 1/5] checks if any aggregators have been set --- .../query/queryplan/ConceptQueryPlan.java | 3 + .../SIMPLE_QUERY_NO_AGGREGATON/content.csv | 5 ++ .../SIMPLE_QUERY_NO_AGGREGATON/expected.csv | 3 + .../query.test.json | 66 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/content.csv create mode 100644 backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/expected.csv create mode 100644 backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/query.test.json diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/queryplan/ConceptQueryPlan.java b/backend/src/main/java/com/bakdata/conquery/models/query/queryplan/ConceptQueryPlan.java index aaae5d0e51..0b938d556f 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/queryplan/ConceptQueryPlan.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/queryplan/ConceptQueryPlan.java @@ -155,6 +155,9 @@ public Optional> getValidityDateAggregator() { } public boolean isAggregateValidityDates() { + if (aggregators.isEmpty()) { + return false; + } return dateAggregator.equals(aggregators.get(0)); } diff --git a/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/content.csv b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/content.csv new file mode 100644 index 0000000000..d421e0428d --- /dev/null +++ b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/content.csv @@ -0,0 +1,5 @@ +pid,datum,test_column +1,2012-01-01,"A1" +2,2010-07-15,"B2" +3,2013-11-10,"A1" +4,2012-11-11,"B2" diff --git a/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/expected.csv b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/expected.csv new file mode 100644 index 0000000000..5bfb4ffc8c --- /dev/null +++ b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/expected.csv @@ -0,0 +1,3 @@ +result +1 +3 \ No newline at end of file diff --git a/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/query.test.json b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/query.test.json new file mode 100644 index 0000000000..ff0dafbcd5 --- /dev/null +++ b/backend/src/test/resources/tests/query/SIMPLE_QUERY_NO_AGGREGATON/query.test.json @@ -0,0 +1,66 @@ +{ + "type": "QUERY_TEST", + "label": "SIMPLE_QUERY_NO_AGGREGATON Test", + "expectedCsv": "tests/query/SIMPLE_QUERY_NO_AGGREGATON/expected.csv", + "query": { + "type": "CONCEPT_QUERY", + "dateAggregationMode": "NONE", + "root": { + "type": "CONCEPT", + "ids": [ + "test_tree.test_child1" + ], + "tables": [ + { + "id": "test_tree.test_column" + } + ] + } + }, + "concepts": [ + { + "label": "test_tree", + "type":"TREE", + "connectors": { + "label": "tree_label", + "name": "test_column", + "column": "test_table.test_column", + "validityDates": {"label": "datum" ,"column": "test_table.datum"} + }, + "children": [ + { + "label": "test_child1", + "description": " ", + "condition": {"type": "PREFIX_LIST", "prefixes": "A1"} + }, + { + "label": "test_child2", + "description": " ", + "condition": {"type": "PREFIX_LIST", "prefixes": "B2"} + } + ] + } + ], + "content": { + "tables": [ + { + "csv": "tests/query/SIMPLE_QUERY_NO_AGGREGATON/content.csv", + "name": "test_table", + "primaryColumn" : { + "name": "pid", + "type": "STRING" + }, + "columns": [ + { + "name": "datum", + "type": "DATE" + }, + { + "name": "test_column", + "type": "STRING" + } + ] + } + ] + } +} From f7242e94532b9372d4180c52c64f242ed4ab9353 Mon Sep 17 00:00:00 2001 From: Fabian Kovacs <1553491+awildturtok@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:58:41 +0100 Subject: [PATCH 2/5] revert using name, when Tag is set --- .../com/bakdata/conquery/models/preproc/PreprocessingJob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/preproc/PreprocessingJob.java b/backend/src/main/java/com/bakdata/conquery/models/preproc/PreprocessingJob.java index 6aab19ae29..ae6dd22d73 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/preproc/PreprocessingJob.java +++ b/backend/src/main/java/com/bakdata/conquery/models/preproc/PreprocessingJob.java @@ -34,7 +34,7 @@ public File getPreprocessedFile() { } // With tag, name is overwritten to tag - return preprocessedOut.resolve(descriptor.getName() + "." + tag.get() + EXTENSION_PREPROCESSED).toFile(); + return preprocessedOut.resolve(descriptor.getTable() + "." + tag.get() + EXTENSION_PREPROCESSED).toFile(); } public long estimateTotalCsvSizeBytes() { From df2779031600c8b39494bf6a1f178b780c8d7277 Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Wed, 10 Nov 2021 22:13:52 +0100 Subject: [PATCH 3/5] give more context for unknown errors and log them manager node --- .../conquery/models/error/ConqueryError.java | 19 +++++++++++++++++-- .../models/execution/ManagedExecution.java | 5 +++-- .../conquery/models/query/QueryExecutor.java | 3 ++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java b/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java index c266cd63b0..3bc1183cf4 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java +++ b/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java @@ -20,6 +20,9 @@ import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; + +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -110,19 +113,31 @@ public ContextError(String messageTemplate, ConqueryError cause) { @Slf4j @CPSType(base = ConqueryError.class, id = "CQ_UNKNOWN_ERROR") - public static class UnknownError extends NoContextError { + public static class UnknownError extends ContextError { + + private final static String UNKNOWN_ERROR_CLASS = "UNKNOWN_ERROR_CLASS"; + private final static String UNKNOWN_ERROR_MESSAGE = "UNKNOWN_ERROR_MESSAGE"; + private final static String UNKNOWN_ERROR_STACKTRACE = "UNKNOWN_ERROR_STACKTRACE"; /** * Constructor for deserialization. */ @JsonCreator private UnknownError() { - super("An unknown error occured"); + super("An unknown error occurred: ${" + UNKNOWN_ERROR_CLASS + "} - ${" + UNKNOWN_ERROR_MESSAGE + "}\n${" + UNKNOWN_ERROR_STACKTRACE + "}"); } public UnknownError(Throwable e) { this(); log.error("Encountered unknown Error[{}]", this.getId(), e); + getContext().put(UNKNOWN_ERROR_CLASS, e.getClass().getSimpleName()); + getContext().put(UNKNOWN_ERROR_MESSAGE, e.getMessage()); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stackTrace = sw.toString(); + getContext().put(UNKNOWN_ERROR_STACKTRACE, stackTrace); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java b/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java index 7b7116b35c..1295eb7aab 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java +++ b/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java @@ -170,8 +170,9 @@ protected void fail(MetaStorage storage, ConqueryErrorInfo error) { } else { this.error = error; - // Log the error, so its id is atleast once in the logs - log.warn("The execution [{}] failed with:\n\t{}", this.getId(), this.error); + // Log the error, so its id is at least once in the logs + // Indent the unknown stacktrace so it can be better distinguished + log.warn("The execution [{}] failed with (the error is from one of the shard nodes):\n\t{}", this.getId(), this.error.getMessage().replaceAll("\\n", "\n\t")); } finish(storage, ExecutionState.FAILED); diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/QueryExecutor.java b/backend/src/main/java/com/bakdata/conquery/models/query/QueryExecutor.java index fc2a6be2b3..1ba7850c51 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/QueryExecutor.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/QueryExecutor.java @@ -82,8 +82,9 @@ public boolean execute(Query query, QueryExecutionContext executionContext, Shar return true; } catch (Exception e) { + // Just trace here. If it's wrapped as an UnknownError, it will be logged anyway. + log.trace("Error while executing {}", executionContext.getExecutionId(), e); ConqueryError err = asConqueryError(e); - log.warn("Error while executing {}", executionContext.getExecutionId(), err); sendFailureToManagerNode(result, asConqueryError(err)); return false; } From 5666868afda7565524c1f80f06df4bd56b6d9483 Mon Sep 17 00:00:00 2001 From: Max Thonagel <12283268+thoniTUB@users.noreply.github.com> Date: Thu, 11 Nov 2021 08:56:19 +0100 Subject: [PATCH 4/5] fixes serdes test --- .../bakdata/conquery/models/error/ConqueryError.java | 11 ++++++----- .../bakdata/conquery/models/SerializationTests.java | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java b/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java index 3bc1183cf4..c0c9183a9d 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java +++ b/backend/src/main/java/com/bakdata/conquery/models/error/ConqueryError.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -127,15 +128,15 @@ private UnknownError() { super("An unknown error occurred: ${" + UNKNOWN_ERROR_CLASS + "} - ${" + UNKNOWN_ERROR_MESSAGE + "}\n${" + UNKNOWN_ERROR_STACKTRACE + "}"); } - public UnknownError(Throwable e) { + public UnknownError(@NonNull Throwable cause) { this(); - log.error("Encountered unknown Error[{}]", this.getId(), e); - getContext().put(UNKNOWN_ERROR_CLASS, e.getClass().getSimpleName()); - getContext().put(UNKNOWN_ERROR_MESSAGE, e.getMessage()); + log.error("Encountered unknown Error[{}]", this.getId(), cause); + getContext().put(UNKNOWN_ERROR_CLASS, cause.getClass().getSimpleName()); + getContext().put(UNKNOWN_ERROR_MESSAGE, cause.getMessage()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); + cause.printStackTrace(pw); String stackTrace = sw.toString(); getContext().put(UNKNOWN_ERROR_STACKTRACE, stackTrace); } diff --git a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java index ab753a9840..79c217c98a 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java +++ b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java @@ -322,7 +322,7 @@ public void executionCreationResolveError() throws JSONException, IOException { @Test public void executionQueryJobError() throws JSONException, IOException { log.info("Beware, this test will print an ERROR message."); - ConqueryError error = new ConqueryError.ExecutionJobErrorWrapper(new Entity(5), new ConqueryError.UnknownError(null)); + ConqueryError error = new ConqueryError.ExecutionJobErrorWrapper(new Entity(5), new ConqueryError.UnknownError(new Exception("This is just a test cause"))); SerializationTestUtil .forType(ConqueryError.class) From 6b443b83f818b7dd5bba5940e685ed75f6af9b50 Mon Sep 17 00:00:00 2001 From: MT <12283268+thoniTUB@users.noreply.github.com> Date: Mon, 15 Nov 2021 12:24:48 +0100 Subject: [PATCH 5/5] Revert "Feature/api keys" --- .../java/com/bakdata/conquery/Constants.java | 4 +- .../com/bakdata/conquery/apiv1/ApiV1.java | 16 +- .../conquery/apiv1/FormConfigPatch.java | 12 +- .../bakdata/conquery/apiv1/MetaDataPatch.java | 20 +- .../conquery/apiv1/QueryProcessor.java | 59 +++-- .../auth/ApiTokenDataRepresentation.java | 62 ----- .../bakdata/conquery/apiv1/forms/Form.java | 9 +- .../apiv1/query/QueryDescription.java | 13 +- .../io/jackson/ConquerySerializersModule.java | 9 +- .../CharArrayBufferDeserializer.java | 30 --- .../serializer/CharArrayBufferSerializer.java | 22 -- .../conquery/io/jersey/RESTServer.java | 4 +- .../arrow/ResultArrowFileProcessor.java | 6 +- .../io/result/arrow/ResultArrowProcessor.java | 18 +- .../arrow/ResultArrowStreamProcessor.java | 6 +- .../io/result/csv/ResultCsvProcessor.java | 18 +- .../io/result/excel/ResultExcelProcessor.java | 14 +- .../bakdata/conquery/io/storage/Store.java | 2 +- .../io/storage/xodus/stores/BigStore.java | 6 +- .../io/storage/xodus/stores/CachedStore.java | 4 +- .../xodus/stores/KeyIncludingStore.java | 2 +- .../xodus/stores/SerializingStore.java | 4 +- .../storage/xodus/stores/WeakCachedStore.java | 4 +- .../io/storage/xodus/stores/XodusStore.java | 10 +- .../models/auth/AuthorizationController.java | 8 +- .../models/auth/AuthorizationHelper.java | 46 ++-- .../auth/ConqueryAuthenticationInfo.java | 20 +- .../auth/ConqueryAuthenticationRealm.java | 28 +-- .../models/auth/ConqueryAuthenticator.java | 41 +++- .../auth/ConqueryAuthorizationRealm.java | 7 +- .../models/auth/apitoken/ApiToken.java | 50 ---- .../models/auth/apitoken/ApiTokenCreator.java | 64 ----- .../models/auth/apitoken/ApiTokenData.java | 98 -------- .../models/auth/apitoken/ApiTokenHash.java | 36 --- .../models/auth/apitoken/ApiTokenRealm.java | 180 --------------- .../auth/apitoken/PrintableASCIIProvider.java | 35 --- .../conquery/models/auth/apitoken/Scopes.java | 43 ---- .../models/auth/apitoken/TokenScopedUser.java | 109 --------- .../models/auth/apitoken/TokenStorage.java | 215 ----------------- .../auth/basic/LocalAuthenticationRealm.java | 6 +- .../models/auth/basic/PasswordHasher.java | 3 - .../conquerytoken/ConqueryTokenRealm.java | 20 +- .../auth/develop/DefaultInitialUserRealm.java | 16 +- .../models/auth/develop/DevAuthConfig.java | 3 +- .../models/auth/entities/PermissionOwner.java | 11 +- .../conquery/models/auth/entities/Role.java | 11 +- .../models/auth/entities/RoleOwner.java | 7 +- .../models/auth/entities/Subject.java | 42 ---- .../conquery/models/auth/entities/User.java | 34 +-- .../oidc/IntrospectionDelegatingRealm.java | 21 +- .../auth/oidc/JwtPkceVerifyingRealm.java | 189 +++++++-------- .../auth/util/SinglePrincipalCollection.java | 109 +++++++++ .../auth/util/SubjectPrincipalCollection.java | 87 ------- .../models/auth/web/DefaultAuthFilter.java | 14 +- .../models/config/FrontendConfig.java | 4 +- .../models/config/XodusStoreFactory.java | 6 +- .../config/auth/ApiTokenRealmFactory.java | 92 -------- .../auth/AuthenticationRealmFactory.java | 1 - .../config/auth/AuthorizationConfig.java | 8 +- .../auth/DefaultAuthorizationConfig.java | 10 +- .../auth/DevelopmentAuthorizationConfig.java | 11 +- .../auth/JwtPkceVerifyingRealmFactory.java | 2 +- .../auth/LocalAuthenticationConfig.java | 3 +- .../concepts/FrontEndConceptBuilder.java | 10 +- .../specific/AbstractSelectFilter.java | 2 +- .../models/execution/ManagedExecution.java | 23 +- .../conquery/models/execution/Shareable.java | 8 +- .../models/forms/configs/FormConfig.java | 7 +- .../FormConfigProcessor.java | 60 +++-- .../frontendconfiguration/FormProcessor.java | 6 +- .../models/forms/managed/ManagedForm.java | 5 +- .../identifiable/ids/specific/UserId.java | 8 +- .../conquery/models/query/ManagedQuery.java | 9 +- .../resources/admin/rest/AdminResource.java | 11 +- ...DatasetsResource.java => APIResource.java} | 7 +- .../resources/api/ApiTokenResource.java | 61 ----- .../resources/api/ConceptsProcessor.java | 10 +- .../resources/api/DatasetResource.java | 2 +- .../resources/api/FormConfigResource.java | 22 +- .../conquery/resources/api/FormResource.java | 6 +- .../conquery/resources/api/MeResource.java | 2 +- .../conquery/resources/api/QueryResource.java | 64 ++--- .../api/ResultArrowFileResource.java | 8 +- .../api/ResultArrowStreamResource.java | 8 +- .../resources/api/ResultCsvResource.java | 10 +- .../resources/api/ResultExcelResource.java | 8 +- .../resources/hierarchies/HAdmin.java | 6 +- .../resources/hierarchies/HAuthorized.java | 12 +- .../resources/hierarchies/HConcepts.java | 2 +- .../resources/hierarchies/HDatasets.java | 2 +- .../api/form/config/FormConfigTest.java | 2 +- .../conquery/integration/IntegrationTest.java | 4 +- .../integration/IntegrationTests.java | 8 +- .../integration/json/JsonIntegrationTest.java | 3 +- .../integration/tests/ApiTokenRealmTest.java | 218 ------------------ .../conquery/models/SerializationTests.java | 32 --- .../conquery/models/auth/ApiTokenTest.java | 34 --- .../IntrospectionDelegatingRealmTest.java | 33 ++- .../models/auth/LocalAuthRealmTest.java | 2 +- .../auth/oidc/JwtPkceVerifyingRealmTest.java | 43 ++-- .../conquery/util/NonPersistentStore.java | 2 +- .../conquery/util/support/TestConquery.java | 31 +-- .../tests/endpoints/apiEndpointInfo.json | 2 +- docs/Config JSON.md | 17 +- docs/REST API JSONs.md | 32 +-- 105 files changed, 732 insertions(+), 2154 deletions(-) delete mode 100644 backend/src/main/java/com/bakdata/conquery/apiv1/auth/ApiTokenDataRepresentation.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferDeserializer.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferSerializer.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiToken.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenCreator.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenData.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenHash.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/PrintableASCIIProvider.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/Scopes.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenScopedUser.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenStorage.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/entities/Subject.java create mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/util/SinglePrincipalCollection.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/auth/util/SubjectPrincipalCollection.java delete mode 100644 backend/src/main/java/com/bakdata/conquery/models/config/auth/ApiTokenRealmFactory.java rename backend/src/main/java/com/bakdata/conquery/resources/api/{DatasetsResource.java => APIResource.java} (86%) delete mode 100644 backend/src/main/java/com/bakdata/conquery/resources/api/ApiTokenResource.java delete mode 100644 backend/src/test/java/com/bakdata/conquery/integration/tests/ApiTokenRealmTest.java delete mode 100644 backend/src/test/java/com/bakdata/conquery/models/auth/ApiTokenTest.java diff --git a/autodoc/src/main/java/com/bakdata/conquery/Constants.java b/autodoc/src/main/java/com/bakdata/conquery/Constants.java index 7ed2feb870..291680ed0d 100644 --- a/autodoc/src/main/java/com/bakdata/conquery/Constants.java +++ b/autodoc/src/main/java/com/bakdata/conquery/Constants.java @@ -69,11 +69,11 @@ import com.bakdata.conquery.models.preproc.TableImportDescriptor; import com.bakdata.conquery.models.preproc.TableInputDescriptor; import com.bakdata.conquery.models.preproc.outputs.OutputDescription; +import com.bakdata.conquery.resources.api.APIResource; import com.bakdata.conquery.resources.api.ConceptResource; import com.bakdata.conquery.resources.api.ConceptsProcessor; import com.bakdata.conquery.resources.api.ConfigResource; import com.bakdata.conquery.resources.api.DatasetResource; -import com.bakdata.conquery.resources.api.DatasetsResource; import com.bakdata.conquery.resources.api.FilterResource; import com.bakdata.conquery.resources.api.QueryResource; import com.bakdata.conquery.resources.api.ResultCsvResource; @@ -138,7 +138,7 @@ public class Constants { .build(), Group.builder().name("REST API JSONs") .resource(ConfigResource.class) - .resource(DatasetsResource.class) + .resource(APIResource.class) .resource(DatasetResource.class) .resource(ConceptResource.class) .resource(FilterResource.class) diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/ApiV1.java b/backend/src/main/java/com/bakdata/conquery/apiv1/ApiV1.java index d78d024f9a..03bfd5de79 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/ApiV1.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/ApiV1.java @@ -1,5 +1,7 @@ package com.bakdata.conquery.apiv1; +import java.time.Duration; + import com.bakdata.conquery.commands.ManagerNode; import com.bakdata.conquery.io.cps.CPSType; import com.bakdata.conquery.io.jackson.IdRefPathParamConverterProvider; @@ -13,12 +15,18 @@ import com.bakdata.conquery.models.forms.frontendconfiguration.FormConfigProcessor; import com.bakdata.conquery.models.worker.DatasetRegistry; import com.bakdata.conquery.resources.ResourcesProvider; -import com.bakdata.conquery.resources.api.*; +import com.bakdata.conquery.resources.api.APIResource; +import com.bakdata.conquery.resources.api.ConceptResource; +import com.bakdata.conquery.resources.api.ConceptsProcessor; +import com.bakdata.conquery.resources.api.ConfigResource; +import com.bakdata.conquery.resources.api.DatasetResource; +import com.bakdata.conquery.resources.api.FilterResource; +import com.bakdata.conquery.resources.api.FormConfigResource; +import com.bakdata.conquery.resources.api.MeResource; +import com.bakdata.conquery.resources.api.QueryResource; import io.dropwizard.jersey.setup.JerseyEnvironment; import org.glassfish.hk2.utilities.binding.AbstractBinder; -import java.time.Duration; - @CPSType(base = ResourcesProvider.class, id = "ApiV1") public class ApiV1 implements ResourcesProvider { @@ -64,7 +72,7 @@ protected void configure() { environment.register(new ConfigResource(manager.getConfig())); environment.register(FormConfigResource.class); - environment.register(DatasetsResource.class); + environment.register(APIResource.class); environment.register(ConceptResource.class); environment.register(DatasetResource.class); environment.register(FilterResource.class); diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/FormConfigPatch.java b/backend/src/main/java/com/bakdata/conquery/apiv1/FormConfigPatch.java index 0c6862a753..a76dd99467 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/FormConfigPatch.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/FormConfigPatch.java @@ -3,7 +3,7 @@ import java.util.function.Consumer; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.forms.configs.FormConfig; import com.bakdata.conquery.util.QueryUtils; @@ -23,15 +23,15 @@ public class FormConfigPatch extends MetaDataPatch { private JsonNode values; - public void applyTo(FormConfig instance, MetaStorage storage, Subject subject){ - chain(QueryUtils.getNoOpEntryPoint(), storage, subject, instance) + public void applyTo(FormConfig instance, MetaStorage storage, User user){ + chain(QueryUtils.getNoOpEntryPoint(), storage, user, instance) .accept(this); } - protected Consumer chain(Consumer patchConsumerChain, MetaStorage storage, Subject subject, FormConfig instance) { - patchConsumerChain = super.buildChain(patchConsumerChain, storage, subject, instance); + protected Consumer chain(Consumer patchConsumerChain, MetaStorage storage, User user, FormConfig instance) { + patchConsumerChain = super.buildChain(patchConsumerChain, storage, user, instance); - if(getValues() != null && subject.isPermitted(instance,Ability.MODIFY)) { + if(getValues() != null && user.isPermitted(instance,Ability.MODIFY)) { patchConsumerChain = patchConsumerChain.andThen(instance.valueSetter()); } return patchConsumerChain; diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/MetaDataPatch.java b/backend/src/main/java/com/bakdata/conquery/apiv1/MetaDataPatch.java index 19acaf4b32..8989e65509 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/MetaDataPatch.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/MetaDataPatch.java @@ -7,7 +7,7 @@ import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.Group; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.Authorized; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; @@ -37,33 +37,33 @@ public class MetaDataPatch implements Taggable, Labelable, ShareInformation { private List groups; /** - * Patches the given {@link Identifiable} by checking if the subject holds the necessary Permission for that operation. + * Patches the given {@link Identifiable} by checking if the user holds the necessary Permission for that operation. * Hence the patched instance must have a corresponding {@link Permission}-type. * Tagging and Labeling only alters the state of the instance while sharing also alters the state of {@link Group}s. * * @param instance The instance to patch * @param storage Storage that persists the instance and also auth information. - * @param subject The subject on whose behalf the patch is executed + * @param user The user on whose behalf the patch is executed * @param Type of the instance that is patched */ - public , INST extends Taggable & Shareable & Labelable & Identifiable & Owned & Authorized> void applyTo(INST instance, MetaStorage storage, Subject subject) { + public , INST extends Taggable & Shareable & Labelable & Identifiable & Owned & Authorized> void applyTo(INST instance, MetaStorage storage, User user) { buildChain(QueryUtils.getNoOpEntryPoint(), storage, - subject, + user, instance ) .accept(this); } - protected , INST extends Taggable & Shareable & Labelable & Identifiable & Owned & Authorized> Consumer buildChain(Consumer patchConsumerChain, MetaStorage storage, Subject subject, INST instance) { - if (getTags() != null && subject.isPermitted(instance, Ability.TAG)) { + protected , INST extends Taggable & Shareable & Labelable & Identifiable & Owned & Authorized> Consumer buildChain(Consumer patchConsumerChain, MetaStorage storage, User user, INST instance) { + if (getTags() != null && user.isPermitted(instance, Ability.TAG)) { patchConsumerChain = patchConsumerChain.andThen(instance.tagger()); } - if (getLabel() != null && subject.isPermitted(instance, Ability.LABEL)) { + if (getLabel() != null && user.isPermitted(instance, Ability.LABEL)) { patchConsumerChain = patchConsumerChain.andThen(instance.labeler()); } - if (getGroups() != null && subject.isPermitted(instance, Ability.SHARE)) { - patchConsumerChain = patchConsumerChain.andThen(instance.sharer(storage, subject)); + if (getGroups() != null && user.isPermitted(instance, Ability.SHARE)) { + patchConsumerChain = patchConsumerChain.andThen(instance.sharer(storage, user)); } return patchConsumerChain; } diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/QueryProcessor.java b/backend/src/main/java/com/bakdata/conquery/apiv1/QueryProcessor.java index 8cd8b84571..425da80a6d 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/QueryProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/QueryProcessor.java @@ -32,7 +32,6 @@ import com.bakdata.conquery.models.auth.AuthorizationHelper; import com.bakdata.conquery.models.auth.entities.Group; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; @@ -69,9 +68,9 @@ public class QueryProcessor { * Creates a query for all datasets, then submits it for execution on the * intended dataset. */ - public ManagedExecution postQuery(Dataset dataset, QueryDescription query, Subject subject) { + public ManagedExecution postQuery(Dataset dataset, QueryDescription query, User user) { - log.info("Query posted on Dataset[{}] by User[{{}].", dataset.getId(), subject.getId()); + log.info("Query posted on Dataset[{}] by User[{{}].", dataset.getId(), user.getId()); // This maps works as long as we have query visitors that are not configured in anyway. // So adding a visitor twice would replace the previous one but both would have yielded the same result. @@ -83,7 +82,7 @@ public ManagedExecution postQuery(Dataset dataset, QueryDescription query, Su visitors.putInstance(QueryUtils.OnlyReusingChecker.class, new QueryUtils.OnlyReusingChecker()); visitors.putInstance(NamespacedIdentifiableCollector.class, new NamespacedIdentifiableCollector()); - final String primaryGroupName = AuthorizationHelper.getPrimaryGroup(subject, storage).map(Group::getName).orElse("none"); + final String primaryGroupName = AuthorizationHelper.getPrimaryGroup(user, storage).map(Group::getName).orElse("none"); visitors.putInstance(ExecutionMetrics.QueryMetricsReporter.class, new ExecutionMetrics.QueryMetricsReporter(primaryGroupName)); @@ -98,8 +97,7 @@ public ManagedExecution postQuery(Dataset dataset, QueryDescription query, Su query.visit(consumerChain); - query.authorize(subject, dataset, visitors); - // After all authorization checks we can now use the actual subject to invoke the query and do not to bubble down the Userish in methods + query.authorize(user, dataset, visitors); ExecutionMetrics.reportNamespacedIds(visitors.getInstance(NamespacedIdentifiableCollector.class).getIdentifiables(), primaryGroupName); @@ -113,7 +111,7 @@ public ManagedExecution postQuery(Dataset dataset, QueryDescription query, Su { final Optional executionId = visitors.getInstance(QueryUtils.OnlyReusingChecker.class).getOnlyReused(); - final Optional> execution = executionId.map(id -> tryReuse(query, id, datasetRegistry, config, executionManager, subject.getUser())); + final Optional> execution = executionId.map(id -> tryReuse(query, id, datasetRegistry, config, executionManager, user)); if (execution.isPresent()) { return execution.get(); @@ -121,7 +119,7 @@ public ManagedExecution postQuery(Dataset dataset, QueryDescription query, Su } // Execute the query - return executionManager.runQuery(datasetRegistry, query, subject.getUser(), dataset, config); + return executionManager.runQuery(datasetRegistry, query, user, dataset, config); } /** @@ -175,14 +173,14 @@ private ManagedExecution tryReuse(QueryDescription query, ManagedExecutionId } - public Stream getAllQueries(Dataset dataset, HttpServletRequest req, Subject subject, boolean allProviders) { + public Stream getAllQueries(Dataset dataset, HttpServletRequest req, User user, boolean allProviders) { Collection> allQueries = storage.getAllExecutions(); - return getQueriesFiltered(dataset, RequestAwareUriBuilder.fromRequest(req), subject, allQueries, allProviders); + return getQueriesFiltered(dataset, RequestAwareUriBuilder.fromRequest(req), user, allQueries, allProviders); } - public Stream getQueriesFiltered(Dataset datasetId, UriBuilder uriBuilder, Subject subject, Collection> allQueries, boolean allProviders) { - Map> datasetAbilities = buildDatasetAbilityMap(subject, datasetRegistry); + public Stream getQueriesFiltered(Dataset datasetId, UriBuilder uriBuilder, User user, Collection> allQueries, boolean allProviders) { + Map> datasetAbilities = buildDatasetAbilityMap(user, datasetRegistry); return allQueries.stream() // The following only checks the dataset, under which the query was submitted, but a query can target more that @@ -191,11 +189,13 @@ public Stream getQueriesFiltered(Dataset datasetId, UriBuilder // to exclude subtypes from somewhere else .filter(QueryProcessor::canFrontendRender) .filter(q -> q.getState().equals(ExecutionState.DONE) || q.getState().equals(ExecutionState.NEW)) - .filter(q -> subject.isPermitted(q, Ability.READ)) + // We decide, that if a user owns an execution it is permitted to see it, which saves us a lot of permissions + // However, for other executions we check because those are probably shared. + .filter(q -> user.isPermitted(q, Ability.READ)) .map(mq -> { OverviewExecutionStatus status = mq.buildStatusOverview( uriBuilder.clone(), - subject + user ); if (mq.isReadyToDownload(datasetAbilities)) { setDownloadUrls(status, config.getResultProviders(), mq, uriBuilder, allProviders); @@ -262,14 +262,14 @@ public static boolean isFrontendStructure(CQElement root) { /** * Cancel a running query: Sending cancellation to shards, which will cause them to stop executing them, results are not sent back, and incoming results will be discarded. */ - public void cancel(Subject subject, Dataset dataset, ManagedExecution query) { + public void cancel(User user, Dataset dataset, ManagedExecution query) { // Does not make sense to cancel a query that isn't running. if (!query.getState().equals(ExecutionState.RUNNING)) { return; } - log.info("User[{}] cancelled Query[{}]", subject.getId(), query.getId()); + log.info("{} cancelled Query[{}]", user, query.getId()); final Namespace namespace = getDatasetRegistry().get(dataset.getId()); @@ -278,14 +278,13 @@ public void cancel(Subject subject, Dataset dataset, ManagedExecution query) namespace.sendToAll(new CancelQuery(query.getId())); } - public void patchQuery(Subject subject, ManagedExecution execution, MetaDataPatch patch) { + public void patchQuery(User user, ManagedExecution execution, MetaDataPatch patch) { log.info("Patching {} ({}) with patch: {}", execution.getClass().getSimpleName(), execution, patch); - patch.applyTo(execution, storage, subject); + patch.applyTo(execution, storage, user); storage.updateExecution(execution); - // TODO remove this, since we don't translate anymore // Patch this query in other datasets List remainingDatasets = datasetRegistry.getAllDatasets(); remainingDatasets.remove(execution.getDataset()); @@ -297,13 +296,13 @@ public void patchQuery(Subject subject, ManagedExecution execution, MetaDataP continue; } log.trace("Patching {} ({}) with patch: {}", execution.getClass().getSimpleName(), id, patch); - patch.applyTo(otherExecution, storage, subject); + patch.applyTo(otherExecution, storage, user); storage.updateExecution(execution); } } - public void reexecute(Subject subject, ManagedExecution query) { - log.info("User[{}] reexecuted Query[{}]", subject.getId(), query); + public void reexecute(User user, ManagedExecution query) { + log.info("User[{}] reexecuted Query[{}]", user, query); if (!query.getState().equals(ExecutionState.RUNNING)) { datasetRegistry.get(query.getDataset().getId()) @@ -313,8 +312,8 @@ public void reexecute(Subject subject, ManagedExecution query) { } - public void deleteQuery(Subject subject, ManagedExecution execution) { - log.info("User[{}] deleted Query[{}]", subject.getId(), execution.getId()); + public void deleteQuery(User user, ManagedExecution execution) { + log.info("User[{}] deleted Query[{}]", user.getId(), execution.getId()); datasetRegistry.get(execution.getDataset().getId()) .getExecutionManager() // Don't go over execution#getExecutionManager() as that's only set when query is initialized @@ -323,12 +322,12 @@ public void deleteQuery(Subject subject, ManagedExecution execution) { storage.removeExecution(execution.getId()); } - public FullExecutionStatus getQueryFullStatus(ManagedExecution query, Subject subject, UriBuilder url, Boolean allProviders) { + public FullExecutionStatus getQueryFullStatus(ManagedExecution query, User user, UriBuilder url, Boolean allProviders) { query.initExecutable(datasetRegistry, config); - Map> datasetAbilities = buildDatasetAbilityMap(subject, datasetRegistry); - final FullExecutionStatus status = query.buildStatusFull(storage, subject, datasetRegistry, config); + Map> datasetAbilities = buildDatasetAbilityMap(user, datasetRegistry); + final FullExecutionStatus status = query.buildStatusFull(storage, user, datasetRegistry, config); if (query.isReadyToDownload(datasetAbilities)) { setDownloadUrls(status, config.getResultProviders(), query, url, allProviders); @@ -337,9 +336,9 @@ public FullExecutionStatus getQueryFullStatus(ManagedExecution query, Subject } /** - * Try to resolve the external upload, if successful, create query for the subject and return id and statistics for that. + * Try to resolve the external upload, if successful, create query for the user and return id and statistics for that. */ - public ExternalUploadResult uploadEntities(Subject subject, Dataset dataset, ExternalUpload upload) { + public ExternalUploadResult uploadEntities(User user, Dataset dataset, ExternalUpload upload) { final CQExternal.ResolveStatistic statistic = CQExternal.resolveEntities(upload.getValues(), upload.getFormat(), @@ -360,7 +359,7 @@ public ExternalUploadResult uploadEntities(Subject subject, Dataset dataset, Ext // We only create the Query, really no need to execute it as it's only useful for composition. final ManagedQuery execution = ((ManagedQuery) datasetRegistry.get(dataset.getId()).getExecutionManager() - .createExecution(datasetRegistry, query, subject.getUser(), dataset)); + .createExecution(datasetRegistry, query, user, dataset)); execution.setLastResultCount((long) statistic.getResolved().size()); diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/auth/ApiTokenDataRepresentation.java b/backend/src/main/java/com/bakdata/conquery/apiv1/auth/ApiTokenDataRepresentation.java deleted file mode 100644 index 8af65cd398..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/auth/ApiTokenDataRepresentation.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.bakdata.conquery.apiv1.auth; - -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenData; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenRealm; -import com.bakdata.conquery.models.auth.apitoken.Scopes; -import com.bakdata.conquery.models.auth.entities.User; -import io.dropwizard.validation.ValidationMethod; -import lombok.*; -import lombok.experimental.Accessors; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.time.LocalDate; -import java.util.Set; -import java.util.UUID; - -/** - * Container class for how tokens are represented through the API. - * This is necessary so that the actual token and it's hash are not leaked (with except for the token on creation). - * @implNote We don't use fluent accessors here, because that does not work well with Jackson - */ -@Data -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class ApiTokenDataRepresentation { - - @NotNull - protected String name; - @NotNull - protected LocalDate expirationDate; - @NotEmpty - protected Set scopes; - - @ValidationMethod - boolean isNotExpired() { - final LocalDate now = LocalDate.now(); - return expirationDate.isAfter(now) || expirationDate.isEqual(now); - } - - /** - * Container that is send with an incoming request to create a token. - */ - @Data - @EqualsAndHashCode(callSuper = true) - public static class Request extends ApiTokenDataRepresentation { - // Intentionally left blank - } - - /** - * Container that is send with an outgoing response to give information about created tokens. - */ - @Data - @EqualsAndHashCode(callSuper = true) - public static class Response extends ApiTokenDataRepresentation { - - private UUID id; - private LocalDate lastUsed; - private LocalDate creationDate; - private boolean isExpired; - - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/forms/Form.java b/backend/src/main/java/com/bakdata/conquery/apiv1/forms/Form.java index 280e2d0b4b..acf43762d3 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/forms/Form.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/forms/Form.java @@ -6,7 +6,6 @@ import com.bakdata.conquery.apiv1.query.QueryDescription; import com.bakdata.conquery.io.cps.CPSType; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.forms.frontendconfiguration.FormScanner; @@ -31,10 +30,10 @@ public String getFormType() { @Override - public void authorize(Subject subject, Dataset submittedDataset, @NonNull ClassToInstanceMap visitors) { - QueryDescription.super.authorize(subject, submittedDataset, visitors); - // Check if subject is allowed to create this form - subject.authorize(FormScanner.FRONTEND_FORM_CONFIGS.get(getFormType()), Ability.CREATE); + public void authorize(User user, Dataset submittedDataset, @NonNull ClassToInstanceMap visitors) { + QueryDescription.super.authorize(user, submittedDataset, visitors); + // Check if user is allowed to create this form + user.authorize(FormScanner.FRONTEND_FORM_CONFIGS.get(getFormType()), Ability.CREATE); } diff --git a/backend/src/main/java/com/bakdata/conquery/apiv1/query/QueryDescription.java b/backend/src/main/java/com/bakdata/conquery/apiv1/query/QueryDescription.java index 1fea1cb186..8d68074cce 100644 --- a/backend/src/main/java/com/bakdata/conquery/apiv1/query/QueryDescription.java +++ b/backend/src/main/java/com/bakdata/conquery/apiv1/query/QueryDescription.java @@ -7,7 +7,6 @@ import com.bakdata.conquery.io.cps.CPSBase; import com.bakdata.conquery.io.jackson.InternalOnly; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; @@ -68,7 +67,7 @@ default void addVisitors(@NonNull ClassToInstanceMap visitors) { /** * Check implementation specific permissions. Is called after all visitors have been registered and executed. */ - default void authorize(Subject subject, Dataset submittedDataset, @NonNull ClassToInstanceMap visitors) { + default void authorize(User user, Dataset submittedDataset, @NonNull ClassToInstanceMap visitors) { NamespacedIdentifiableCollector nsIdCollector = QueryUtils.getVisitor(visitors, NamespacedIdentifiableCollector.class); ExternalIdChecker externalIdChecker = QueryUtils.getVisitor(visitors, QueryUtils.ExternalIdChecker.class); if(nsIdCollector == null) { @@ -79,7 +78,7 @@ default void authorize(Subject subject, Dataset submittedDataset, @NonNull Class .map(NamespacedIdentifiable::getDataset) .collect(Collectors.toSet()); - subject.authorize(datasets, Ability.READ); + user.authorize(datasets, Ability.READ); // Generate ConceptPermissions final Set concepts = nsIdCollector.getIdentifiables().stream() @@ -88,13 +87,13 @@ default void authorize(Subject subject, Dataset submittedDataset, @NonNull Class .map(ConceptElement::getConcept) .collect(Collectors.toSet()); - subject.authorize(concepts, Ability.READ); + user.authorize(concepts, Ability.READ); - subject.authorize(collectRequiredQueries(), Ability.READ); + user.authorize(collectRequiredQueries(), Ability.READ); - // Check if the query contains parts that require to resolve external IDs. If so the subject must have the preserve_id permission on the dataset. + // Check if the query contains parts that require to resolve external IDs. If so the user must have the preserve_id permission on the dataset. if(externalIdChecker.resolvesExternalIds()) { - subject.authorize(submittedDataset, Ability.PRESERVE_ID); + user.authorize(submittedDataset, Ability.PRESERVE_ID); } } diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java index 1aa39e5668..d2ed362093 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java +++ b/backend/src/main/java/com/bakdata/conquery/io/jackson/ConquerySerializersModule.java @@ -5,7 +5,11 @@ import java.util.List; import com.bakdata.conquery.io.cps.CPSTypeIdResolver; -import com.bakdata.conquery.io.jackson.serializer.*; +import com.bakdata.conquery.io.jackson.serializer.ClassToInstanceMapDeserializer; +import com.bakdata.conquery.io.jackson.serializer.ConqueryDoubleSerializer; +import com.bakdata.conquery.io.jackson.serializer.CurrencyUnitDeserializer; +import com.bakdata.conquery.io.jackson.serializer.CurrencyUnitSerializer; +import com.bakdata.conquery.io.jackson.serializer.IdKeyDeserializer; import com.bakdata.conquery.models.datasets.concepts.MatchingStats; import com.bakdata.conquery.models.identifiable.ids.IId; import com.fasterxml.jackson.databind.DeserializationContext; @@ -18,7 +22,6 @@ import groovyjarjarantlr4.v4.runtime.misc.IntSet; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.apache.http.util.CharArrayBuffer; public class ConquerySerializersModule extends SimpleModule { @@ -54,7 +57,5 @@ public Object createUsingDefault(DeserializationContext ctxt) throws IOException addKeyDeserializer(type, new IdKeyDeserializer<>()); } addSerializer(new ConqueryDoubleSerializer()); - addDeserializer(CharArrayBuffer.class, new CharArrayBufferDeserializer()); - addSerializer(CharArrayBuffer.class, new CharArrayBufferSerializer()); } } \ No newline at end of file diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferDeserializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferDeserializer.java deleted file mode 100644 index 59cddf77ee..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferDeserializer.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.bakdata.conquery.io.jackson.serializer; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import org.apache.http.util.CharArrayBuffer; - -import java.io.IOException; -import java.util.Arrays; - -/** - * This custom serializer treats an incoming text node as a mutable string, which is important for sensitve data. - * Doing so, allows us to clear buffers. Since {@link String} is immutable it is inappropriate. - */ -public class CharArrayBufferDeserializer extends JsonDeserializer { - @Override - public CharArrayBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { - final JsonToken currentToken = p.getCurrentToken(); - if(currentToken != JsonToken.VALUE_STRING){ - ctxt.handleUnexpectedToken(CharArrayBuffer.class, currentToken, p,"cannot deserialize CharArrayBuffer"); - } - final char[] chars = p.readValueAs(char[].class); - final CharArrayBuffer charArrayBuffer = new CharArrayBuffer(chars.length); - charArrayBuffer.append(chars, 0, chars.length); - Arrays.fill(chars, '\0'); - return charArrayBuffer; - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferSerializer.java b/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferSerializer.java deleted file mode 100644 index 860229a8ff..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/io/jackson/serializer/CharArrayBufferSerializer.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.bakdata.conquery.io.jackson.serializer; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import org.apache.http.util.CharArrayBuffer; - -import java.io.IOException; -import java.util.Arrays; - -/** - * This serializer avoids that the character sequence is transformed into an immutable String, with which we would lose control over invalidation of its content. - */ -public class CharArrayBufferSerializer extends JsonSerializer { - @Override - public void serialize(CharArrayBuffer value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - final char[] buffer = value.buffer(); - gen.writeString(buffer, 0, value.length()); - value.clear(); - Arrays.fill(buffer, '\0'); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/io/jersey/RESTServer.java b/backend/src/main/java/com/bakdata/conquery/io/jersey/RESTServer.java index c3fb1a72dd..0d7d99940e 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/jersey/RESTServer.java +++ b/backend/src/main/java/com/bakdata/conquery/io/jersey/RESTServer.java @@ -7,7 +7,7 @@ import com.bakdata.conquery.io.jetty.ConqueryErrorExecptionMapper; import com.bakdata.conquery.io.jetty.ConqueryJsonExceptionMapper; import com.bakdata.conquery.io.jetty.JsonValidationExceptionMapper; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.web.AuthenticationExceptionMapper; import com.bakdata.conquery.models.auth.web.AuthorizationExceptionMapper; import com.bakdata.conquery.models.config.ConqueryConfig; @@ -23,7 +23,7 @@ public class RESTServer { public static void configure(ConqueryConfig config, ResourceConfig jersey) { // Bind User class to REST authentication - jersey.register(new AuthValueFactoryProvider.Binder<>(Subject.class)); + jersey.register(new AuthValueFactoryProvider.Binder<>(User.class)); //change exception mapper behavior because of JERSEY-2437 //https://github.com/eclipse-ee4j/jersey/issues/2709 ((DefaultServerFactory) config.getServerFactory()).setRegisterDefaultExceptionMappers(false); diff --git a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowFileProcessor.java b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowFileProcessor.java index 76e6e2ba22..0ed7142f2f 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowFileProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowFileProcessor.java @@ -8,7 +8,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ManagedExecution; @@ -26,10 +26,10 @@ public class ResultArrowFileProcessor { private final DatasetRegistry datasetRegistry; private final ConqueryConfig config; - public & SingleTableResult> Response getArrowFileResult(Subject subject, E exec, Dataset dataset, boolean pretty) { + public & SingleTableResult> Response getArrowFileResult(User user, E exec, Dataset dataset, boolean pretty) { return getArrowResult( (output) -> (root) -> new ArrowFileWriter(root, new DictionaryProvider.MapDictionaryProvider(), Channels.newChannel(output)), - subject, + user, exec, dataset, datasetRegistry, diff --git a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowProcessor.java b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowProcessor.java index b2fd6ee7c5..74735d422b 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowProcessor.java @@ -15,7 +15,7 @@ import javax.ws.rs.core.StreamingOutput; import com.bakdata.conquery.io.result.ResultUtil; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; @@ -43,7 +43,7 @@ public class ResultArrowProcessor { public static & SingleTableResult> Response getArrowResult( Function> writerProducer, - Subject subject, + User user, E exec, Dataset dataset, DatasetRegistry datasetRegistry, @@ -54,16 +54,16 @@ public static & SingleTableResult> Response getAr final Namespace namespace = datasetRegistry.get(dataset.getId()); - ConqueryMDC.setLocation(subject.getName()); + ConqueryMDC.setLocation(user.getName()); log.info("Downloading results for {} on dataset {}", exec, dataset); - subject.authorize(dataset, Ability.READ); - subject.authorize(dataset, Ability.DOWNLOAD); + user.authorize(dataset, Ability.READ); + user.authorize(dataset, Ability.DOWNLOAD); - subject.authorize(exec, Ability.READ); + user.authorize(exec, Ability.READ); - // Check if subject is permitted to download on all datasets that were referenced by the query - authorizeDownloadDatasets(subject, exec); + // Check if user is permitted to download on all datasets that were referenced by the query + authorizeDownloadDatasets(user, exec); if (!(exec instanceof ManagedQuery || (exec instanceof ManagedForm && ((ManagedForm) exec).getSubQueries().size() == 1))) { return Response.status(HttpStatus.SC_UNPROCESSABLE_ENTITY, "Execution result is not a single Table").build(); @@ -72,7 +72,7 @@ public static & SingleTableResult> Response getAr // Get the locale extracted by the LocaleFilter - IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(subject, exec, namespace); + IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(user, exec, namespace); final Locale locale = I18n.LOCALE.get(); PrintSettings settings = new PrintSettings( pretty, diff --git a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowStreamProcessor.java b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowStreamProcessor.java index b6c86af825..c65cb5a770 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowStreamProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/io/result/arrow/ResultArrowStreamProcessor.java @@ -6,7 +6,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ManagedExecution; @@ -26,10 +26,10 @@ public class ResultArrowStreamProcessor { private final ConqueryConfig config; - public & SingleTableResult> Response getArrowStreamResult(Subject subject, E exec, Dataset dataset, boolean pretty) { + public & SingleTableResult> Response getArrowStreamResult(User user, E exec, Dataset dataset, boolean pretty) { return getArrowResult( (output) -> (root) -> new ArrowStreamWriter(root, new DictionaryProvider.MapDictionaryProvider(), output), - subject, + user, exec, dataset, datasetRegistry, diff --git a/backend/src/main/java/com/bakdata/conquery/io/result/csv/ResultCsvProcessor.java b/backend/src/main/java/com/bakdata/conquery/io/result/csv/ResultCsvProcessor.java index 3dc079457c..f0221de80f 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/result/csv/ResultCsvProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/io/result/csv/ResultCsvProcessor.java @@ -15,7 +15,7 @@ import javax.ws.rs.core.StreamingOutput; import com.bakdata.conquery.io.result.ResultUtil; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; @@ -39,19 +39,19 @@ public class ResultCsvProcessor { private final ConqueryConfig config; - public & SingleTableResult> Response getResult(Subject subject, Dataset dataset, E exec, String userAgent, String queryCharset, boolean pretty) { + public & SingleTableResult> Response getResult(User user, Dataset dataset, E exec, String userAgent, String queryCharset, boolean pretty) { final Namespace namespace = datasetRegistry.get(dataset.getId()); - ConqueryMDC.setLocation(subject.getName()); + ConqueryMDC.setLocation(user.getName()); log.info("Downloading results for {} on dataset {}", exec, dataset); - subject.authorize(namespace.getDataset(), Ability.READ); - subject.authorize(namespace.getDataset(), Ability.DOWNLOAD); + user.authorize(namespace.getDataset(), Ability.READ); + user.authorize(namespace.getDataset(), Ability.DOWNLOAD); - subject.authorize(exec, Ability.READ); + user.authorize(exec, Ability.READ); - // Check if subject is permitted to download on all datasets that were referenced by the query - authorizeDownloadDatasets(subject, exec); + // Check if user is permitted to download on all datasets that were referenced by the query + authorizeDownloadDatasets(user, exec); - IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(subject, exec, namespace); + IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(user, exec, namespace); // Get the locale extracted by the LocaleFilter diff --git a/backend/src/main/java/com/bakdata/conquery/io/result/excel/ResultExcelProcessor.java b/backend/src/main/java/com/bakdata/conquery/io/result/excel/ResultExcelProcessor.java index 751ce2d89d..f0d0c9bc45 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/result/excel/ResultExcelProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/io/result/excel/ResultExcelProcessor.java @@ -11,7 +11,7 @@ import javax.ws.rs.core.StreamingOutput; import com.bakdata.conquery.io.result.ResultUtil; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; @@ -36,16 +36,16 @@ public class ResultExcelProcessor { private final ConqueryConfig config; - public & SingleTableResult> Response getExcelResult(Subject subject, E exec, DatasetId datasetId, boolean pretty) { - ConqueryMDC.setLocation(subject.getName()); + public & SingleTableResult> Response getExcelResult(User user, E exec, DatasetId datasetId, boolean pretty) { + ConqueryMDC.setLocation(user.getName()); final Namespace namespace = datasetRegistry.get(datasetId); Dataset dataset = namespace.getDataset(); - subject.authorize(dataset, Ability.READ); - subject.authorize(dataset, Ability.DOWNLOAD); - subject.authorize(exec, Ability.READ); + user.authorize(dataset, Ability.READ); + user.authorize(dataset, Ability.DOWNLOAD); + user.authorize(exec, Ability.READ); - IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(subject,exec,namespace); + IdPrinter idPrinter = config.getFrontend().getQueryUpload().getIdPrinter(user,exec,namespace); final Locale locale = I18n.LOCALE.get(); PrintSettings settings = new PrintSettings( diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/Store.java b/backend/src/main/java/com/bakdata/conquery/io/storage/Store.java index bc652345ca..99d5813709 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/Store.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/Store.java @@ -36,7 +36,7 @@ public interface StoreEntryConsumer { void clear(); - void deleteStore(); + void removeStore(); void close() throws IOException; } diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/BigStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/BigStore.java index 556c561b6d..d9dfd81659 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/BigStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/BigStore.java @@ -239,8 +239,8 @@ public void clear() { } @Override - public void deleteStore() { - metaStore.deleteStore(); - dataStore.deleteStore(); + public void removeStore() { + metaStore.removeStore(); + dataStore.removeStore(); } } diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/CachedStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/CachedStore.java index 8639349dd2..6d672b823b 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/CachedStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/CachedStore.java @@ -133,8 +133,8 @@ public void clear() { } @Override - public void deleteStore() { - store.deleteStore(); + public void removeStore() { + store.removeStore(); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/KeyIncludingStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/KeyIncludingStore.java index 9d1dbdf6ca..5178a0b466 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/KeyIncludingStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/KeyIncludingStore.java @@ -73,7 +73,7 @@ public void clear() { } public void removeStore() { - store.deleteStore(); + store.removeStore(); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/SerializingStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/SerializingStore.java index 0a8f7295ce..e9d64151ca 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/SerializingStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/SerializingStore.java @@ -394,8 +394,8 @@ public void clear() { } @Override - public void deleteStore() { - store.deleteStore(); + public void removeStore() { + store.remove(); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/WeakCachedStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/WeakCachedStore.java index 590b81a651..be125c3296 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/WeakCachedStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/WeakCachedStore.java @@ -114,9 +114,9 @@ public void clear() { } @Override - public void deleteStore() { + public void removeStore() { cache.invalidateAll(); - store.deleteStore(); + store.removeStore(); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/XodusStore.java b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/XodusStore.java index e75a3ea74d..6d3522f36c 100644 --- a/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/XodusStore.java +++ b/backend/src/main/java/com/bakdata/conquery/io/storage/xodus/stores/XodusStore.java @@ -98,15 +98,19 @@ public void clear() { }); } - public void deleteStore() { - log.debug("Deleting store {} from environment {}", store.getName(), environment.getLocation()); + public void remove() { +// if (!environment.isOpen()) { +// log.debug("While removing store: Environment is already closed for {}", this); +// return; +// } + log.debug("Removing store {} from environment {}", store.getName(), environment.getLocation()); environment.executeInTransaction(t -> environment.removeStore(store.getName(),t)); storeRemoveHook.accept(this); } public void close() { if (!environment.isOpen()) { - log.trace("While closing store: Environment is already closed for {}", this); + log.debug("While closing store: Environment is already closed for {}", this); return; } storeCloseHook.accept(this); diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationController.java b/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationController.java index b73e587b82..b8de0ca93f 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationController.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationController.java @@ -9,9 +9,7 @@ import com.bakdata.conquery.apiv1.auth.ProtoUser; import com.bakdata.conquery.commands.ManagerNode; -import com.bakdata.conquery.io.jackson.Jackson; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenRealm; import com.bakdata.conquery.models.auth.basic.JWTokenHandler; import com.bakdata.conquery.models.auth.conquerytoken.ConqueryTokenRealm; import com.bakdata.conquery.models.auth.entities.User; @@ -23,7 +21,6 @@ import com.bakdata.conquery.models.config.auth.AuthenticationRealmFactory; import com.bakdata.conquery.models.config.auth.AuthorizationConfig; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import io.dropwizard.jersey.validation.Validators; import io.dropwizard.lifecycle.Managed; import io.dropwizard.util.Strings; import lombok.Getter; @@ -77,7 +74,7 @@ public AuthorizationController(MetaStorage storage, AuthorizationConfig authoriz redirectingAuthFilter = new RedirectingAuthFilter(authenticationFilter); - // Add the user token realm + // Add the central authentication realm conqueryTokenRealm = new ConqueryTokenRealm(storage); authenticationRealms.add(conqueryTokenRealm); realms.add(conqueryTokenRealm); @@ -86,7 +83,6 @@ public AuthorizationController(MetaStorage storage, AuthorizationConfig authoriz // Add the central authorization realm AuthorizingRealm authorizingRealm = new ConqueryAuthorizationRealm(storage); realms.add(authorizingRealm); - securityManager = new DefaultSecurityManager(realms); ModularRealmAuthenticator authenticator = (ModularRealmAuthenticator) securityManager.getAuthenticator(); authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy()); @@ -172,7 +168,7 @@ public static User flatCopyUser(@NonNull User originUser, String namePrefix, @No // Find a new user id that is not used yet String name = null; do { - name = namePrefix + UUID.randomUUID() + originUserId.getName(); + name = namePrefix + UUID.randomUUID() + originUserId.getEmail(); } while (storage.getUser(new UserId(name)) != null); // Retrieve original user and its effective permissions diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationHelper.java b/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationHelper.java index 6e4cce1391..236447c187 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationHelper.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/AuthorizationHelper.java @@ -15,7 +15,6 @@ import com.bakdata.conquery.models.auth.entities.Group; import com.bakdata.conquery.models.auth.entities.Role; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.datasets.Dataset; @@ -38,12 +37,12 @@ @UtilityClass public class AuthorizationHelper { - public static List getGroupsOf(@NonNull Subject subject, @NonNull MetaStorage storage){ + public static List getGroupsOf(@NonNull User user, @NonNull MetaStorage storage){ List userGroups = new ArrayList<>(); for (Group group : storage.getAllGroups()) { - if(group.containsMember(subject.getUser())) { + if(group.containsMember(user)) { userGroups.add(group); } } @@ -51,15 +50,15 @@ public static List getGroupsOf(@NonNull Subject subject, @NonNull MetaSto } /** - * Find the primary group of the subject. All users must have a primary group. - * @implNote Currently this is the first group of a subject and should also be the only group. + * Find the primary group of the user. All users must have a primary group. + * @implNote Currently this is the first group of a user and should also be the only group. */ - public static Optional getPrimaryGroup(@NonNull Subject subject, @NonNull MetaStorage storage) { - List groups = getGroupsOf(subject, storage); + public static Optional getPrimaryGroup(@NonNull User user, @NonNull MetaStorage storage) { + List groups = getGroupsOf(user, storage); if(groups.isEmpty()) { return Optional.empty(); } - // TODO: 17.02.2020 implement primary flag for group + // TODO: 17.02.2020 implement primary flag for user etc. return Optional.of(groups.get(0)); } @@ -94,7 +93,7 @@ public static List getGroupsByRole(MetaStorage storage, Role role) { * Checks if an execution is allowed to be downloaded by a user. * This checks all used {@link DatasetId}s for the {@link Ability#DOWNLOAD} on the user. */ - public static void authorizeDownloadDatasets(@NonNull Subject subject, @NonNull Visitable visitable) { + public static void authorizeDownloadDatasets(@NonNull User user, @NonNull Visitable visitable) { NamespacedIdentifiableCollector collector = new NamespacedIdentifiableCollector(); visitable.visit(collector); @@ -104,28 +103,45 @@ public static void authorizeDownloadDatasets(@NonNull Subject subject, @NonNull .map(NamespacedIdentifiable::getDataset) .collect(Collectors.toSet()); - subject.authorize(datasets, Ability.DOWNLOAD); + user.authorize(datasets, Ability.DOWNLOAD); + } + + /** + * Checks if a {@link Visitable} has only references to {@link Dataset}s a user is allowed to read. + * This checks all used {@link DatasetId}s for the {@link Ability#READ} on the user. + */ + public static void authorizeReadDatasets(@NonNull User user, @NonNull Visitable visitable) { + NamespacedIdentifiableCollector collector = new NamespacedIdentifiableCollector(); + visitable.visit(collector); + + Set datasets = + collector.getIdentifiables() + .stream() + .map(NamespacedIdentifiable::getDataset) + .collect(Collectors.toSet()); + + user.authorize(datasets, Ability.READ); } /** - * Calculates the abilities on all datasets a subject has based on its permissions. + * Calculates the abilities on all datasets a user has based on its permissions. */ - public static Map> buildDatasetAbilityMap(Subject subject, DatasetRegistry datasetRegistry) { + public static Map> buildDatasetAbilityMap(User user, DatasetRegistry datasetRegistry) { HashMap> datasetAbilities = new HashMap<>(); for (Dataset dataset : datasetRegistry.getAllDatasets()) { Set abilities = datasetAbilities.computeIfAbsent(dataset.getId(), (k) -> new HashSet<>()); - if(subject.isPermitted(dataset,Ability.READ)) { + if(user.isPermitted(dataset,Ability.READ)) { abilities.add(Ability.READ); } - if (subject.isPermitted(dataset,Ability.DOWNLOAD)){ + if (user.isPermitted(dataset,Ability.DOWNLOAD)){ abilities.add(Ability.DOWNLOAD); } - if (subject.isPermitted(dataset,Ability.PRESERVE_ID)) { + if (user.isPermitted(dataset,Ability.PRESERVE_ID)) { abilities.add(Ability.PRESERVE_ID); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationInfo.java b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationInfo.java index 473ad19f9f..7992ba490c 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationInfo.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationInfo.java @@ -1,12 +1,15 @@ package com.bakdata.conquery.models.auth; -import com.bakdata.conquery.models.auth.entities.Subject; -import com.bakdata.conquery.models.auth.util.SubjectPrincipalCollection; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NonNull; import lombok.experimental.FieldNameConstants; import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.subject.SimplePrincipalCollection; + +import java.util.Collection; /** * Specialization class of the {@link AuthenticationInfo} that enforces the use @@ -18,7 +21,7 @@ @EqualsAndHashCode public class ConqueryAuthenticationInfo implements AuthenticationInfo { - private final SubjectPrincipalCollection principals; + private final SimplePrincipalCollection principals = new SimplePrincipalCollection(); /** * The credential a realm used for authentication. @@ -31,10 +34,17 @@ public class ConqueryAuthenticationInfo implements AuthenticationInfo { */ private final boolean displayLogout; - public ConqueryAuthenticationInfo(Subject subject, Object credentials, ConqueryAuthenticationRealm realm, boolean displayLogout) { + public ConqueryAuthenticationInfo(UserId userId, Object credentials, Realm realm, boolean displayLogout) { this.credentials = credentials; this.displayLogout = displayLogout; - principals = new SubjectPrincipalCollection(subject, realm); + principals.add(userId, realm.getName()); } + public ConqueryAuthenticationInfo(UserId userId, Object credentials, Realm realm, boolean displayLogout, @NonNull Collection alternativeIds) { + this(userId, credentials, realm, displayLogout); + if(!alternativeIds.isEmpty()){ + // The underlying SimplePrincipalCollection denies empty collections + principals.addAll(alternativeIds, realm.getName()); + } + } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationRealm.java index c8afae54dd..e2ad1eee94 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticationRealm.java @@ -4,42 +4,38 @@ import javax.ws.rs.container.ContainerRequestContext; import com.bakdata.conquery.Conquery; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.web.DefaultAuthFilter; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import lombok.RequiredArgsConstructor; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.realm.AuthenticatingRealm; -import org.apache.shiro.realm.Realm; import org.apache.shiro.util.Destroyable; /** - * Interface that needs to be implemented for authenticating realms in + * Abstract class that needs to be implemented for authenticating realms in * Conquery. */ -public interface ConqueryAuthenticationRealm extends Realm { +public abstract class ConqueryAuthenticationRealm extends AuthenticatingRealm implements Destroyable { + + @Override + protected final AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + return doGetConqueryAuthenticationInfo(token); + } /** * Method that all authenticating realms in {@link Conquery} need to implement. * It is a stricter version of * {@link AuthenticatingRealm#doGetAuthenticationInfo}, which enforces a return * type of {@link ConqueryAuthenticationInfo}. - * + * * @param token A token that the realm previously extracted from a request. * @return An {@link ConqueryAuthenticationInfo} containing the UserId of the user that caused the request or {@code null}, which means that no account could be associated with the specified token. * @throws AuthenticationException Upon failed authentication. */ - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; + protected abstract ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; - default User getUserOrThrowUnknownAccount(MetaStorage storage, UserId userId) throws UnknownAccountException { - final User user = storage.getUser(userId); - if (user == null) { - throw new UnknownAccountException("The user id was unknown: " + userId); - } - return user; + @Override + public void destroy() throws Exception { + // Might be implemented if the realm needs to release resources } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticator.java b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticator.java index 43a8f43ea4..ea3b0a0b7f 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticator.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthenticator.java @@ -1,11 +1,15 @@ package com.bakdata.conquery.models.auth; +import java.util.Collection; import java.util.Optional; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.io.storage.MetaStorage; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.web.AuthenticationExceptionMapper; +import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import com.bakdata.conquery.util.io.ConqueryMDC; import io.dropwizard.auth.Authenticator; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; @@ -18,25 +22,40 @@ * We need this authenticator to plug in the security, and hereby shiro, into the AuthFilter. */ @Slf4j -public class ConqueryAuthenticator implements Authenticator{ +@RequiredArgsConstructor +public class ConqueryAuthenticator implements Authenticator{ + private final MetaStorage storage; + /** * The execeptions thrown by Shiro will be catched by {@link AuthenticationExceptionMapper}. */ @Override - public Optional authenticate(AuthenticationToken token) { + public Optional authenticate(AuthenticationToken token) { // Submit the token to Shiro (to all realms that were registered) ConqueryAuthenticationInfo info = (ConqueryAuthenticationInfo) SecurityUtils.getSecurityManager().authenticate(token); + // All authenticating realms must return a UserId as identifying principal - // Extract - Subject subject = info.getPrincipals().oneByType(Subject.class); - + // Used the first matching user id + User user = null; + Collection userIds = info.getPrincipals().byType(UserId.class); + for(UserId userId : userIds) { + user = storage.getUser(userId); + if (user != null) { + break; + } + } + + if(user == null) { + log.trace("None of the provided ids match any user: {}", userIds); + return Optional.empty(); + } - // If the subject was present, all further authorization can now be performed on the subject object - log.trace("Using subject {} for further authorization", subject); - ConqueryMDC.setLocation(subject.getId().toString()); - subject.setAuthenticationInfo(info); - return Optional.of(subject); + // If the user was present, all further authorization can now be performed on the user object + log.trace("Using user {} for further authorization (provided ids: {})", user, userIds ); + ConqueryMDC.setLocation(user.getId().toString()); + user.setDisplayLogout(info.isDisplayLogout()); + return Optional.ofNullable(user); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthorizationRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthorizationRealm.java index de2fd08010..c660693916 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthorizationRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/ConqueryAuthorizationRealm.java @@ -7,7 +7,6 @@ import java.util.Set; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import com.google.common.collect.Sets; import lombok.RequiredArgsConstructor; @@ -26,7 +25,7 @@ */ @RequiredArgsConstructor public class ConqueryAuthorizationRealm extends AuthorizingRealm { - + public final MetaStorage storage; @Override @@ -43,10 +42,10 @@ protected void onInit() { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Objects.requireNonNull(principals, "No principal info was provided"); - Subject subject = principals.oneByType(Subject.class); + UserId userId = (UserId) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new ConqueryAuthorizationInfo(); - info.addObjectPermissions(Collections.unmodifiableSet(subject.getUser().getEffectivePermissions())); + info.addObjectPermissions(Collections.unmodifiableSet(storage.getUser(userId).getEffectivePermissions())); return info; } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiToken.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiToken.java deleted file mode 100644 index 779d597960..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiToken.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import org.apache.http.util.CharArrayBuffer; -import org.apache.shiro.authc.AuthenticationToken; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.nio.CharBuffer; -import java.util.Arrays; - -/** - * Container for a sensitive token that allows to use the APIs (see {@link ApiTokenRealm}) - * This object only carries the authenticating part of the token. The authorizing part is within a corresponding - * {@link ApiTokenData} object. - * - * @implNote After the token is processed, its buffer must be cleared to avoid leakage. Conquery has registered the - * {@link com.bakdata.conquery.io.jackson.serializer.CharArrayBufferSerializer} to post the token once to the user - * through the API. Be aware, that once this object was serialized, its token is cleared. - */ -@RequiredArgsConstructor(onConstructor = @__(@JsonCreator)) -@Getter -public class ApiToken implements AuthenticationToken { - - @NotNull - @NotEmpty - private final CharArrayBuffer token; - - @Override - @JsonIgnore - public CharArrayBuffer getPrincipal() { - return token; - } - - @Override - @JsonIgnore - public CharArrayBuffer getCredentials() { - return token; - } - - - public void clear() { - Arrays.fill(token.buffer(), '\0'); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenCreator.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenCreator.java deleted file mode 100644 index 2e998e4f41..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenCreator.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import lombok.Data; -import org.apache.http.util.CharArrayBuffer; -import org.jetbrains.annotations.TestOnly; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.util.Random; - -/** - * Provider for random generated API tokens. - * - * Use a pseudo random generator for test purpose only. - */ -@Data -public class ApiTokenCreator { - public static final int TOKEN_LENGTH = 37; // GitHub uses 37 alphanumerics for their token - public static final String TOKEN_PREFIX = "cq"; // short for conquery - - private static final String ALGORITHM = "PBKDF2WithHmacSHA1"; - private static final int ITERATIONS = 10000; - private static final int KEY_LENGTH = 256; - private static final byte[] SALT = {'s','t','a','t','i','c','_','s','a','l','t'}; - - private final PrintableASCIIProvider tokenProvider; - - public ApiTokenCreator() { - this(new SecureRandom()); - } - - @TestOnly - public ApiTokenCreator(Random random) { - tokenProvider = new PrintableASCIIProvider(random); - } - - - - public ApiToken createToken(){ - CharArrayBuffer buffer = new CharArrayBuffer(TOKEN_PREFIX.length() + "_".length() + TOKEN_LENGTH); - buffer.append(TOKEN_PREFIX); - buffer.append('_'); - tokenProvider.fillRemaining(buffer); - return new ApiToken(buffer); - } - - public static ApiTokenHash hashToken(ApiToken apiToken){ - PBEKeySpec spec = new PBEKeySpec(apiToken.getCredentials().buffer(), SALT, ITERATIONS, KEY_LENGTH); - SecretKeyFactory f = null; - try { - f = SecretKeyFactory.getInstance(ALGORITHM); - return new ApiTokenHash(f.generateSecret(spec).getEncoded()); - } - catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("The indicated algorithm was not found", e); - } - catch (InvalidKeySpecException e) { - throw new IllegalStateException("The key specification was invalid", e); - } - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenData.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenData.java deleted file mode 100644 index ebb8194c23..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenData.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.permissions.Ability; -import com.bakdata.conquery.models.auth.permissions.AdminPermission; -import com.bakdata.conquery.models.auth.permissions.Authorized; -import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; -import com.bakdata.conquery.models.execution.Owned; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.OptBoolean; -import com.google.common.collect.ImmutableSet; -import lombok.*; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.time.LocalDate; -import java.util.Set; -import java.util.UUID; - -@Getter -@AllArgsConstructor(onConstructor = @__(@JsonCreator)) -public class ApiTokenData implements Authorized, Owned { - - /** - * The id is used to reference the token from outside the realm, i.e. the API. - */ - private final UUID id; - /** - * The hash is the hashed API-token and not the hash of this object. - * It is only used internally in the realm to get a mapping back to the key of this object. - * This is used when a token is deleted: - * - The api provides the token id (UUID) for the token that needs to be deleted - * - The realm queries the storage for the {@link ApiTokenData} with that id - * - The realm gets the token hash from the data - * - The realm uses this token hash to delete the data from the store - */ - @NonNull - private final ApiTokenHash tokenHash; - @NonNull - private final String name; - @NotNull - private final UserId userId; - @NonNull - private final LocalDate creationDate; - /** - * The expiration date can be null: infinite - */ - private final LocalDate expirationDate; - @NotEmpty - private final Set scopes; - - /** - * @implNote This is the only member that would be mutable otherwise. - */ - public Set getScopes() { - return ImmutableSet.copyOf(scopes); - } - - @JacksonInject(useInput = OptBoolean.FALSE) - @NotNull - @EqualsAndHashCode.Exclude - @JsonIgnore - private final MetaStorage storage; - - - @Override - public ConqueryPermission createPermission(Set abilities) { - return AdminPermission.onDomain(); - } - - @Override - @JsonIgnore - public User getOwner() { - return storage.getUser(userId); - } - - /** - * Dynamic information about the token - */ - @Data - public static class MetaData{ - @NotNull - private final LocalDate lastUsed; - } - - public boolean isCoveredByScopes(ConqueryPermission permission) { - for (Scopes scope : scopes) { - if (scope.isPermissionInScope(permission)) { - return true; - } - } - return false; - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenHash.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenHash.java deleted file mode 100644 index 7095fcdfb4..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenHash.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import java.util.Arrays; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.google.common.base.Joiner; -import lombok.Data; -import lombok.RequiredArgsConstructor; - -/** - * An erasable hash for tokens that implements equals based on its contents - */ -@Data -@RequiredArgsConstructor(onConstructor = @__(@JsonCreator)) -public class ApiTokenHash { - private final byte[] hash; - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!ApiTokenHash.class.isAssignableFrom(obj.getClass())) { - return false; - } - return Arrays.equals(hash, ((ApiTokenHash) obj).hash); - } - - public int hashCode() { - return Arrays.hashCode(hash); - } - - public void clear() { - Arrays.fill(hash, (byte) 0); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenRealm.java index bc9bd94c0a..e69de29bb2 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/ApiTokenRealm.java @@ -1,180 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import javax.validation.constraints.NotNull; - -import com.bakdata.conquery.apiv1.auth.ApiTokenDataRepresentation; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; -import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; -import com.bakdata.conquery.models.auth.entities.Subject; -import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.permissions.Ability; -import com.bakdata.conquery.models.auth.util.SkippingCredentialsMatcher; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.ExpiredCredentialsException; -import org.apache.shiro.authc.IncorrectCredentialsException; -import org.apache.shiro.authc.UnknownAccountException; -import org.apache.shiro.realm.AuthenticatingRealm; - - -/** - * This realm provides and checks long-living API tokens. The tokens support a limited scope of actions that is backed - * by the actual permissions of the invoking user. - * - */ -@Slf4j -public class ApiTokenRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm { - - private final MetaStorage storage; - private final TokenStorage tokenStorage; - - private final ApiTokenCreator apiTokenCreator = new ApiTokenCreator(); - - public ApiTokenRealm(MetaStorage storage, TokenStorage tokenStorage) { - this.storage = storage; - this.tokenStorage = tokenStorage; - this.setCredentialsMatcher(SkippingCredentialsMatcher.INSTANCE); - this.setAuthenticationTokenClass(ApiToken.class); - } - - @Override - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - if (!(token instanceof ApiToken)) { - return null; - } - - final ApiToken apiToken = ((ApiToken) token); - ApiTokenHash tokenHash = ApiTokenCreator.hashToken(apiToken); - - // Clear the token - apiToken.clear(); - - - ApiTokenData tokenData = tokenStorage.get(tokenHash); - if (tokenData == null) { - log.trace("Unknown token, cannot map token hash to token data. Aborting authentication"); - throw new IncorrectCredentialsException(); - } - - if (LocalDate.now().isAfter(tokenData.getExpirationDate())) { - log.info("Supplied token expired on: {}", tokenData.getExpirationDate()); - throw new ExpiredCredentialsException("Supplied token is expired"); - } - - final ApiTokenData.MetaData metaData = new ApiTokenData.MetaData(LocalDate.now()); - tokenStorage.updateMetaData(tokenData.getId(), metaData); - - final UserId userId = tokenData.getUserId(); - final User user = storage.getUser(userId); - - if (user == null) { - throw new UnknownAccountException("The UserId does not map to a user: " + userId); - } - - return new ConqueryAuthenticationInfo(new TokenScopedUser(user, tokenData), token, this, false); - } - - - - public ApiToken createApiToken(User user, ApiTokenDataRepresentation.Request tokenRequest) { - - ApiToken token; - - synchronized (this) { - ApiTokenHash hash; - // Generate a token that does not collide with another tokens hash - do { - token = apiTokenCreator.createToken(); - hash = ApiTokenCreator.hashToken(token); - - } while(tokenStorage.get(hash) != null); - - final ApiTokenData apiTokenData = toInternalRepresentation(tokenRequest, user, hash, storage); - - tokenStorage.add(hash, apiTokenData); - } - - return token; - } - - public List listUserToken(Subject subject) { - ArrayList summary = new ArrayList<>(); - - for (Iterator> it = tokenStorage.getAll(); it.hasNext(); ) { - Pair apiToken = it.next(); - // Find all token data belonging to a user - final ApiTokenData data = apiToken.getKey(); - if (!subject.isOwner(data)){ - continue; - } - - // Fill in the response with the details - final ApiTokenDataRepresentation.Response response = new ApiTokenDataRepresentation.Response(); - response.setId(data.getId()); - response.setCreationDate(data.getCreationDate()); - response.setName(data.getName()); - response.setExpirationDate(data.getExpirationDate()); - response.setScopes(data.getScopes()); - response.setExpired(LocalDate.now().isAfter(data.getExpirationDate())); - - // If the token was ever used it should have an meta data entry - ApiTokenData.MetaData meta = apiToken.getValue(); - if (meta != null) { - response.setLastUsed(meta.getLastUsed()); - } - summary.add(response); - } - return summary; - } - - public void deleteToken(@NotNull Subject subject, @NonNull UUID tokenId) { - - Optional tokenOpt = tokenStorage.getByUUID(tokenId); - - if (tokenOpt.isEmpty()) { - log.warn("No token with id {} was found", tokenId); - return; - } - - - final ApiTokenData token = tokenOpt.get(); - - // Only the Owner or a subject with admin capabilities can delete a token - subject.authorize(token, Ability.DELETE); - - tokenStorage.deleteToken(token); - } - - - - private static ApiTokenData toInternalRepresentation( - ApiTokenDataRepresentation.Request apiTokenRequest, - User user, - ApiTokenHash hash, - MetaStorage storage) { - final UUID id = UUID.randomUUID(); - log.info("Creating new api token data for user {} with id: {}", user.getId(), id); - return new ApiTokenData( - id, - hash, - apiTokenRequest.getName(), - user.getId(), - LocalDate.now(), - apiTokenRequest.getExpirationDate(), - apiTokenRequest.getScopes(), - storage - ); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/PrintableASCIIProvider.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/PrintableASCIIProvider.java deleted file mode 100644 index 29306d1988..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/PrintableASCIIProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.CharUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.util.CharArrayBuffer; - -import java.nio.CharBuffer; -import java.util.Random; -import java.util.stream.Collectors; - -/** - * Fills a buffer with random printable ASCII characters that can be used for an {@link ApiToken}. - * See also {@link ApiTokenCreator}. - */ -@RequiredArgsConstructor -public class PrintableASCIIProvider { - private final Random random; - - public void fillRemaining(CharArrayBuffer buffer) { - final int remaining = buffer.capacity() - buffer.length(); - random.ints(0, 128) - .map(i -> { return (char) i;}) - .filter(this::isValidChar) - .limit(remaining) - .mapToObj(c -> (char) c) - .map(String::valueOf) - .forEach(buffer::append); - } - - private boolean isValidChar(int c) { - return CharUtils.isAsciiAlphanumeric((char ) c); - } - -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/Scopes.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/Scopes.java deleted file mode 100644 index 31aa584be3..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/Scopes.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import com.bakdata.conquery.models.auth.permissions.*; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; - -import java.util.Set; - -/** - * Scopes to group permission types and restrict permissions of {@link ApiToken} (see {@link ApiTokenData}). - */ -@RequiredArgsConstructor -public enum Scopes { - /** - * Allows to use the admin interface - */ - ADMIN(Set.of(AdminPermission.DOMAIN)), - /** - * Allows to access entities of a dataset such as the dataset in general and its concepts. - */ - DATASET(Set.of(DatasetPermission.DOMAIN, ConceptPermission.DOMAIN)), - /** - * Allows to create and use execution related entities such as Queries and {@link com.bakdata.conquery.models.forms.configs.FormConfig}s - */ - EXECUTIONS(Set.of(ExecutionPermission.DOMAIN, FormConfigPermission.DOMAIN, FormPermission.DOMAIN)); - - @NonNull - @JsonIgnore - private final Set scopeDomains; - - /** - * The permission is covered by the scope if all domains in the permission - * are covered by the scope's domains. - * @param permission the permission to test. - * @return True, if all domains are supported by the scope. - * - * @implNote At the moment we use only one domain per permission. - */ - boolean isPermissionInScope(@NonNull ConqueryPermission permission) { - return scopeDomains.containsAll(permission.getDomains()); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenScopedUser.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenScopedUser.java deleted file mode 100644 index ac060167ce..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenScopedUser.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; -import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; -import com.bakdata.conquery.models.auth.permissions.Ability; -import com.bakdata.conquery.models.auth.permissions.Authorized; -import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import org.apache.shiro.authz.UnauthorizedException; - -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -/** - * This subject combines a {@link User} with a belonging {@link ApiTokenData}. Permissions for an authorization request are filtered based on the {@link Scopes} of the token. - * If a permission is covered by a scope the authorization is delegated to the actual user. - */ -@RequiredArgsConstructor -@Getter -public class TokenScopedUser implements Subject { - - private final User delegate; - private final ApiTokenData tokenContext; - - @Override - public UserId getId() { - return delegate.getId(); - } - - @Override - public void authorize(@NonNull Authorized object, @NonNull Ability ability) { - final ConqueryPermission permission = object.createPermission(EnumSet.of(ability)); - if(!tokenContext.isCoveredByScopes(permission)) { - throw new UnauthorizedException("The scopes of the token do not support handling the permission: " + permission); - } - delegate.authorize(object,ability); - } - - @Override - public void authorize(Set objects, Ability ability) { - final EnumSet abilityEnumSet = EnumSet.of(ability); - if(!objects.stream().map(o -> o.createPermission(abilityEnumSet)).allMatch(tokenContext::isCoveredByScopes)) { - throw new UnauthorizedException("The scopes of the tokens do not support handling the permission"); - } - delegate.authorize(objects, ability); - } - - @Override - public boolean isPermitted(Authorized object, Ability ability) { - final ConqueryPermission permission = object.createPermission(EnumSet.of(ability)); - if(!tokenContext.isCoveredByScopes(permission)) { - return false; - } - return delegate.isPermitted(object, ability); - } - - @Override - public boolean isPermittedAll(Collection authorized, Ability ability) { - final EnumSet abilitySet = EnumSet.of(ability); - if(!authorized.stream().map(o -> o.createPermission(abilitySet)).allMatch(tokenContext::isCoveredByScopes)) { - return false; - } - return delegate.isPermittedAll(authorized,ability); - } - - @Override - public boolean[] isPermitted(List authorized, Ability ability) { - final EnumSet abilitySet = EnumSet.of(ability); - - boolean[] ret = new boolean[authorized.size()]; - for (int i = 0; i < ret.length; i++) { - Authorized object = authorized.get(i); - ret[i] = tokenContext.isCoveredByScopes(object.createPermission(abilitySet)) && - delegate.isPermitted(object, ability); - } - return ret; - } - - @Override - public boolean isOwner(Authorized object) { - return delegate.isOwner(object); - } - - @Override - public boolean isDisplayLogout() { - return false; - } - - @Override - public void setAuthenticationInfo(ConqueryAuthenticationInfo info) { - delegate.setAuthenticationInfo(info); - } - - @Override - public User getUser() { - return delegate; - } - - @Override - public String getName() { - return delegate.getName(); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenStorage.java b/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenStorage.java deleted file mode 100644 index c42b24ec0c..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/apitoken/TokenStorage.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.bakdata.conquery.models.auth.apitoken; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import javax.validation.Validator; - -import com.bakdata.conquery.io.storage.Store; -import com.bakdata.conquery.io.storage.StoreMappings; -import com.bakdata.conquery.io.storage.xodus.stores.SerializingStore; -import com.bakdata.conquery.io.storage.xodus.stores.XodusStore; -import com.bakdata.conquery.models.config.XodusConfig; -import com.bakdata.conquery.util.io.FileUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.dropwizard.lifecycle.Managed; -import jetbrains.exodus.ExodusException; -import jetbrains.exodus.env.Environment; -import jetbrains.exodus.env.EnvironmentClosedException; -import jetbrains.exodus.env.Environments; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; - -/** - * Storage for immutable token data (the counter part to the {@link ApiToken}/{@link ApiTokenHash} that is presented to the user once) and - * the token meta data (which is updated on each usage of the token). - * - * @implNote While this has the two store layout resembles a {@link com.bakdata.conquery.io.storage.xodus.stores.BigStore}, a BigStore's meta store - * exists to hide a 1-n relationship (1 entry in meta, n entries in data) between a key an the chunks of its value. This store has a 1-1 relation ship between - * meta and data. The meta store is not used to store structural information but actual meta data about the token in the data store. - */ -@Slf4j -@RequiredArgsConstructor -public class TokenStorage implements Managed { - - private static final int ENVIRONMENT_CLOSING_RETRIES = 2; - private static final int ENVIRONMENT_CLOSING_TIMEOUT = 2; // seconds - - private final Path storageDir; - private final XodusConfig storeConfig; - private final Validator validator; - private final ObjectMapper objectMapper; - - private Environment environment; - private Store dataStore; - private Store metaDataStore; - private ArrayList openStoresInEnv = new ArrayList<>(); - - @Override - public void start(){ - String storeName = "api-token"; - File tokenStore = new File(storageDir.toFile(), storeName); - environment = Environments.newInstance(tokenStore, storeConfig.createConfig()); - - final XodusStore data = new XodusStore( - environment, - "DATA", - this::closeStoreHook, - this::removeStoreHook - ); - dataStore = StoreMappings.cached(new SerializingStore<>( - data, - validator, - objectMapper, - ApiTokenHash.class, - ApiTokenData.class, - true, - false, - null - )); - openStoresInEnv.add(data); - - final XodusStore meta = new XodusStore( - environment, - "META", - this::closeStoreHook, - this::removeStoreHook - ); - metaDataStore = StoreMappings.cached(new SerializingStore<>( - meta, - validator, - objectMapper, - UUID.class, - ApiTokenData.MetaData.class, - true, - false, - null - )); - openStoresInEnv.add(meta); - } - - - - private void removeStoreHook(XodusStore store) { - openStoresInEnv.remove(store); - - if (!openStoresInEnv.isEmpty()){ - return; - } - - final Environment environment = store.getEnvironment(); - log.info("Removed last XodusStore in Environment. Removing Environment as well: {}", environment.getLocation()); - - final List xodusStores= environment.computeInReadonlyTransaction(environment::getAllStoreNames); - - if (!xodusStores.isEmpty()){ - throw new IllegalStateException("Cannot delete environment, because it still contains these stores:" + xodusStores); - } - - environment.close(); - - try { - FileUtil.deleteRecursive(Path.of(environment.getLocation())); - } - catch (IOException e) { - log.error("Cannot delete directory of removed Environment[{}]", environment.getLocation(), e); - } - } - - private void closeStoreHook(XodusStore store) { - openStoresInEnv.remove(store); - final Environment environment = store.getEnvironment(); - if (!openStoresInEnv.isEmpty()){ - return; - } - if (!environment.isOpen()) { - return; - } - environment.close(); - } - - - - @Override - public void stop() throws Exception { - if (!environment.isOpen()) { - return; - } - for(int retries = 0; retries < ENVIRONMENT_CLOSING_RETRIES; retries++) { - try { - log.info("Closing the environment."); - environment.close(); - return; - } - catch (EnvironmentClosedException e) { - log.warn("Environment was already closed, which is odd but mayby the stop() lifecycle event fired twice"); - return; - } - catch (ExodusException e) { - if (retries == 0) { - log.info("The environment is still working on some transactions. Retry"); - } - log.info("Waiting for {} seconds to retry.", ENVIRONMENT_CLOSING_TIMEOUT); - Thread.sleep(ENVIRONMENT_CLOSING_TIMEOUT * 1000 /* milliseconds */); - } - } - // Close the environment with force - log.info("Closing the environment forcefully"); - environment.getEnvironmentConfig().setEnvCloseForcedly(true); - environment.close(); - - } - - public ApiTokenData get(ApiTokenHash tokenHash) { - return dataStore.get(tokenHash); - } - - public void updateMetaData(UUID id, ApiTokenData.MetaData metaData) { - metaDataStore.update(id, metaData); - } - - public void add(ApiTokenHash hash, ApiTokenData apiTokenData) { - dataStore.add(hash, apiTokenData); - } - - public Iterator> getAll() { - return dataStore.getAll().stream().map(tokenData -> Pair.of(tokenData, metaDataStore.get(tokenData.getId()))).iterator(); - } - - public Optional getByUUID(UUID tokenId) { - // Find the corresponding token data and extract its hash - for (ApiTokenData apiTokenData : dataStore.getAll()) { - if (tokenId.equals(apiTokenData.getId())) { - return Optional.of(apiTokenData); - } - } - return Optional.empty(); - } - - public void deleteToken(ApiTokenData token) { - - - final ApiTokenHash hash = token.getTokenHash(); - - synchronized (this) { - // This should never return null - ApiTokenData data = dataStore.get(hash); - if (data == null) { - throw new IllegalStateException("Unable to retrieve token data for hash."); - } - - - dataStore.remove(hash); - metaDataStore.remove(token.getId()); - } - - hash.clear(); - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/basic/LocalAuthenticationRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/basic/LocalAuthenticationRealm.java index 2632931fc8..712e845232 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/basic/LocalAuthenticationRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/basic/LocalAuthenticationRealm.java @@ -37,8 +37,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.realm.AuthenticatingRealm; -import org.apache.shiro.util.Destroyable; /** * This realm stores credentials in a local database ({@link XodusStore}). Upon @@ -52,7 +50,7 @@ * through specific endpoints that are registerd by this realm. */ @Slf4j -public class LocalAuthenticationRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm, UserManageable, AccessTokenCreator, Destroyable { +public class LocalAuthenticationRealm extends ConqueryAuthenticationRealm implements UserManageable, AccessTokenCreator { private static final int ENVIRONMNENT_CLOSING_RETRYS = 2; private static final int ENVIRONMNENT_CLOSING_TIMEOUT = 2; // seconds @@ -119,7 +117,7 @@ protected void onInit() { * Should not be called since the tokens are now handled by the ConqueryTokenRealm. */ @Override - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + protected ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { throw new UnsupportedOperationException("Should not be called since the tokens are now handled by the ConqueryTokenRealm."); } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/basic/PasswordHasher.java b/backend/src/main/java/com/bakdata/conquery/models/auth/basic/PasswordHasher.java index 8a12d1cd9f..163cfa052b 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/basic/PasswordHasher.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/basic/PasswordHasher.java @@ -70,9 +70,6 @@ public static byte[] generateHash(char[] password, byte[] salt) { catch (InvalidKeySpecException e) { throw new IllegalStateException("The key specification was invalid", e); } - finally { - spec.clearPassword(); - } } @Data diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/conquerytoken/ConqueryTokenRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/conquerytoken/ConqueryTokenRealm.java index 4b35ba3274..a14064dda0 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/conquerytoken/ConqueryTokenRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/conquerytoken/ConqueryTokenRealm.java @@ -24,14 +24,14 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.*; -import org.apache.shiro.realm.AuthenticatingRealm; @Slf4j -public class ConqueryTokenRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm { +public class ConqueryTokenRealm extends ConqueryAuthenticationRealm { private static final Class TOKEN_CLASS = BearerToken.class; private final MetaStorage storage; + @Setter private JWTConfig jwtConfig = new JWTConfig(); @@ -43,7 +43,7 @@ public ConqueryTokenRealm(MetaStorage storage) { } @Override - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + protected ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { if (!(TOKEN_CLASS.isAssignableFrom(token.getClass()))) { log.trace("Incompatible token. Expected {}, got {}", TOKEN_CLASS, token.getClass()); return null; @@ -74,10 +74,16 @@ public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken to String username = decodedToken.getSubject(); UserId userId = UserId.Parser.INSTANCE.parse(username); + User user = storage.getUser(userId); + // try to construct a new User if none could be found in the storage + if (user == null) { + log.warn( + "Provided credentials were valid, but a corresponding user was not found in the System. You need to add a user to the system with the id: {}", + userId); + return null; + } - final User user = getUserOrThrowUnknownAccount(storage, userId); - - return new ConqueryAuthenticationInfo(user, token, this, true); + return new ConqueryAuthenticationInfo(userId, token, this, true); } @@ -106,7 +112,7 @@ public static class JWTConfig{ private JWTVerifier tokenVerifier; @JsonIgnore - public JWTVerifier getTokenVerifier(AuthenticatingRealm realm) { + public JWTVerifier getTokenVerifier(ConqueryAuthenticationRealm realm) { if(tokenVerifier == null) { tokenVerifier = JWT.require(tokenSignAlgorithm).withIssuer(realm.getName()).build(); } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DefaultInitialUserRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DefaultInitialUserRealm.java index 3af505f74e..9234b77631 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DefaultInitialUserRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DefaultInitialUserRealm.java @@ -1,7 +1,5 @@ package com.bakdata.conquery.models.auth.develop; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.auth.AuthorizationConfig; import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; @@ -10,7 +8,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.realm.AuthenticatingRealm; /** * This realm authenticates requests as if they are effected by the first @@ -21,10 +18,7 @@ * */ @Slf4j -public class DefaultInitialUserRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm { - - - public final MetaStorage storage; +public class DefaultInitialUserRealm extends ConqueryAuthenticationRealm { /** * The warning that is displayed, when the realm is instantiated. @@ -46,20 +40,18 @@ public class DefaultInitialUserRealm extends AuthenticatingRealm implements Conq /** * Standard constructor. */ - public DefaultInitialUserRealm(MetaStorage storage) { + public DefaultInitialUserRealm() { log.warn(WARNING); - this.storage = storage; this.setAuthenticationTokenClass(DevelopmentToken.class); this.setCredentialsMatcher(SkippingCredentialsMatcher.INSTANCE); } @Override - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + protected ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { if (!(token instanceof DevelopmentToken)) { return null; } DevelopmentToken devToken = (DevelopmentToken) token; - final User user = getUserOrThrowUnknownAccount(storage, devToken.getPrincipal()); - return new ConqueryAuthenticationInfo(user, devToken.getCredentials(), this, true); + return new ConqueryAuthenticationInfo(devToken.getPrincipal(), devToken.getCredentials(), this, true); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java b/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java index f6c41a1690..41ae72a414 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java @@ -5,7 +5,6 @@ import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.auth.AuthenticationRealmFactory; -import com.bakdata.conquery.models.config.auth.AuthenticationRealmFactory; /** * Default configuration for the auth system. Sets up all other default components. @@ -24,6 +23,6 @@ public ConqueryAuthenticationRealm createRealm(ManagerNode managerNode) { managerNode.getAuthController().getAuthenticationFilter().registerTokenExtractor(new UserIdTokenExtractor(defaultUser)); - return new DefaultInitialUserRealm(managerNode.getStorage()); + return new DefaultInitialUserRealm(); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/PermissionOwner.java b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/PermissionOwner.java index 55f5c56397..46118c5934 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/PermissionOwner.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/PermissionOwner.java @@ -19,7 +19,6 @@ import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @@ -30,13 +29,10 @@ * The base class of security subjects in this project. Used to represent * persons and groups with permissions. * - * @param The id type by which an instance is identified. - * - * @implNote The NoArgsConstructor is private and used for deserialization + * @param The id type by which an instance is identified */ @Slf4j @EqualsAndHashCode(callSuper = false) -@NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class PermissionOwner>> extends IdentifiableImpl implements Comparable> { private static final Comparator> @@ -144,11 +140,6 @@ public Set getPermissions() { } - /** - * Returns a collection of the effective permissions. These are the permissions of the owner and - * the permission of the roles/groups it inherits from. - * @return Owned and inherited permissions. - */ @JsonIgnore public abstract Set getEffectivePermissions(); diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Role.java b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Role.java index 3d0ec8c1d8..3783c93369 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Role.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Role.java @@ -5,14 +5,9 @@ import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.identifiable.ids.specific.RoleId; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import java.util.Set; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) public class Role extends PermissionOwner { - + public Role(String name, String label, MetaStorage storage) { super(name, label, storage); @@ -27,11 +22,11 @@ public Set getEffectivePermissions() { public RoleId createId() { return new RoleId(name); } - + @Override protected void updateStorage() { storage.updateRole(this); - + } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/RoleOwner.java b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/RoleOwner.java index b2be368dd5..f9ba55109b 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/RoleOwner.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/RoleOwner.java @@ -2,12 +2,8 @@ import java.util.Set; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.identifiable.ids.specific.RoleId; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.collect.Sets; -import lombok.extern.slf4j.Slf4j; public interface RoleOwner { @@ -23,5 +19,4 @@ public interface RoleOwner { //TODO migrate to NsIdRef Role @JsonIgnore Set getRoles(); - -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Subject.java b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Subject.java deleted file mode 100644 index 9227c0999e..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/Subject.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.bakdata.conquery.models.auth.entities; - -import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; -import com.bakdata.conquery.models.auth.permissions.Ability; -import com.bakdata.conquery.models.auth.permissions.Authorized; -import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import lombok.NonNull; - -import java.security.Principal; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * An interface for classes that facade a user or represent a user. - * - * This interface allows realms to present a user different (usually reduced) set of permissions/abilities. - * - **/ -public interface Subject extends Principal { - - UserId getId(); - - void authorize(@NonNull Authorized object, @NonNull Ability ability); - - void authorize(Set objects, Ability ability); - - boolean isPermitted(Authorized object, Ability ability); - - boolean isPermittedAll(Collection authorized, Ability ability); - - boolean[] isPermitted(List authorized, Ability ability); - - boolean isOwner(Authorized object); - - boolean isDisplayLogout(); - - void setAuthenticationInfo(ConqueryAuthenticationInfo info); - - User getUser(); -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/User.java b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/User.java index 7161e59995..5d1c84dc22 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/entities/User.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/entities/User.java @@ -10,10 +10,10 @@ import java.util.stream.Collectors; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.Authorized; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; +import com.bakdata.conquery.models.auth.util.SinglePrincipalCollection; import com.bakdata.conquery.models.execution.Owned; import com.bakdata.conquery.models.identifiable.ids.specific.RoleId; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; @@ -24,6 +24,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; @@ -31,11 +32,16 @@ import org.apache.shiro.subject.PrincipalCollection; @Slf4j -public class User extends PermissionOwner implements Principal, RoleOwner, Subject { +public class User extends PermissionOwner implements Principal, RoleOwner { @JsonProperty private final Set roles = Collections.synchronizedSet(new HashSet<>()); + @Getter + @Setter + @JsonIgnore + private transient boolean displayLogout = true; + // protected for testing purposes @JsonIgnore @Getter(AccessLevel.PROTECTED) @@ -136,34 +142,12 @@ public boolean isOwner(Authorized object) { return object instanceof Owned && equals(((Owned) object).getOwner()); } - @JsonIgnore - @Override - public boolean isDisplayLogout() { - return shiroUserAdapter.getAuthenticationInfo().get().isDisplayLogout(); - } - - @JsonIgnore - @Override - public void setAuthenticationInfo(ConqueryAuthenticationInfo info) { - shiroUserAdapter.getAuthenticationInfo().set(info); - } - - @Override - @JsonIgnore - public User getUser() { - return this; - } - - /** * This class is non static so its a fixed part of the enclosing User object. * Its protected for testing purposes only. */ protected class ShiroUserAdapter extends FilteredUser { - @Getter - private final ThreadLocal authenticationInfo = ThreadLocal.withInitial(() -> new ConqueryAuthenticationInfo(User.this, null, null, false)); - @Override public void checkPermission(Permission permission) throws AuthorizationException { SecurityUtils.getSecurityManager().checkPermission(getPrincipals(), permission); @@ -176,7 +160,7 @@ public void checkPermissions(Collection permissions) throws Authoriz @Override public PrincipalCollection getPrincipals() { - return authenticationInfo.get().getPrincipals(); + return new SinglePrincipalCollection(getId()); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/IntrospectionDelegatingRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/IntrospectionDelegatingRealm.java index a535e69693..bbc2184346 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/IntrospectionDelegatingRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/IntrospectionDelegatingRealm.java @@ -39,7 +39,6 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.BearerToken; import org.apache.shiro.authc.ExpiredCredentialsException; -import org.apache.shiro.realm.AuthenticatingRealm; /** * Realm that validates OpenID access tokens by delegating them to an IDP TokenIntrospection endpoint @@ -47,13 +46,14 @@ @Slf4j @Getter @Setter -public class IntrospectionDelegatingRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm { +@RequiredArgsConstructor +public class IntrospectionDelegatingRealm extends ConqueryAuthenticationRealm { private static final Class TOKEN_CLASS = BearerToken.class; private static final String GROUPS_CLAIM = "groups"; + private final MetaStorage storage; private final IntrospectionDelegatingRealmFactory authProviderConf; - public final MetaStorage storage; private ClientAuthentication clientAuthentication; @@ -65,11 +65,6 @@ public class IntrospectionDelegatingRealm extends AuthenticatingRealm implements .expireAfterWrite(10, TimeUnit.MINUTES) .build(new TokenValidator()); - public IntrospectionDelegatingRealm(MetaStorage storage, IntrospectionDelegatingRealmFactory authProviderConf) { - this.storage = storage; - this.authProviderConf = authProviderConf; - } - @Override protected void onInit() { super.onInit(); @@ -79,7 +74,7 @@ protected void onInit() { @Override @SneakyThrows - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + protected ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { if (!(TOKEN_CLASS.isAssignableFrom(token.getClass()))) { log.trace("Incompatible token. Expected {}, got {}", TOKEN_CLASS, token.getClass()); return null; @@ -92,9 +87,13 @@ public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken to UserId userId = extractId(successResponse); - User user = getUserOrThrowUnknownAccount(storage, userId); + User user = storage.getUser(userId); + + if (user == null) { + throw new IllegalStateException("Unable to retrieve user with id: " + userId); + } - return new ConqueryAuthenticationInfo(user, token, this, true); + return new ConqueryAuthenticationInfo(user.getId(), token, this, true); } private static UserId extractId(TokenIntrospectionSuccessResponse successResponse) { diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealm.java b/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealm.java index f18bfb31f4..f217f98155 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealm.java @@ -1,17 +1,17 @@ package com.bakdata.conquery.models.auth.oidc; -import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.ConqueryAuthenticationInfo; import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; -import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.util.SkippingCredentialsMatcher; import com.bakdata.conquery.models.config.auth.JwtPkceVerifyingRealmFactory; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.authc.*; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.BearerToken; +import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.pam.UnsupportedTokenException; -import org.apache.shiro.realm.AuthenticatingRealm; import org.keycloak.TokenVerifier; import org.keycloak.common.VerificationException; import org.keycloak.exceptions.TokenNotActiveException; @@ -29,106 +29,85 @@ * the authenticated user from it. */ @Slf4j -public class JwtPkceVerifyingRealm extends AuthenticatingRealm implements ConqueryAuthenticationRealm { - - private static final Class TOKEN_CLASS = BearerToken.class; - - Supplier> idpConfigurationSupplier; - private final String[] allowedAudience; - private final TokenVerifier.Predicate[] tokenChecks; - private final List alternativeIdClaims; - private final ActiveWithLeewayVerifier activeVerifier; - private final MetaStorage storage; - - public JwtPkceVerifyingRealm(@NonNull Supplier> idpConfigurationSupplier, - @NonNull String allowedAudience, - List> additionalTokenChecks, - List alternativeIdClaims, - MetaStorage storage, - int tokenLeeway) { - this.storage = storage; - this.idpConfigurationSupplier = idpConfigurationSupplier; - this.allowedAudience = new String[]{allowedAudience}; - this.tokenChecks = additionalTokenChecks.toArray((TokenVerifier.Predicate[]) Array.newInstance(TokenVerifier.Predicate.class, 0)); - this.alternativeIdClaims = alternativeIdClaims; - this.setCredentialsMatcher(SkippingCredentialsMatcher.INSTANCE); - this.setAuthenticationTokenClass(TOKEN_CLASS); - this.activeVerifier = new ActiveWithLeewayVerifier(tokenLeeway); - } - - - @Override - public ConqueryAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - Optional idpConfigurationOpt = idpConfigurationSupplier.get(); - if (idpConfigurationOpt.isEmpty()) { - log.warn("Unable to start authentication, because idp configuration is not available."); - return null; - } - JwtPkceVerifyingRealmFactory.IdpConfiguration idpConfiguration = idpConfigurationOpt.get(); - - log.trace("Creating token verifier"); - TokenVerifier verifier = TokenVerifier.create(((BearerToken) token).getToken(), AccessToken.class) - .withChecks(new TokenVerifier.RealmUrlCheck(idpConfiguration.getIssuer()), TokenVerifier.SUBJECT_EXISTS_CHECK, activeVerifier) - .withChecks(tokenChecks) - .publicKey(idpConfiguration.getPublicKey()) - .audience(allowedAudience); - - String subject; - log.trace("Verifying token"); - AccessToken accessToken = null; - try { - verifier.verify(); - accessToken = verifier.getToken(); - } - catch (VerificationException e) { - log.trace("Verification failed", e); - throw new IncorrectCredentialsException(e); - } - subject = accessToken.getSubject(); - - if (subject == null) { - // Should not happen, as sub is mandatory in an access_token - throw new UnsupportedTokenException("Unable to extract a subject from the provided token."); - } - - log.trace("Authentication successfull for subject {}", subject); - - - UserId userId = new UserId(subject); - User user = storage.getUser(userId); - if (user != null) { - log.trace("Successfully authenticated user {}", userId); - return new ConqueryAuthenticationInfo(user, token, this, true); - } - - // Try alternative ids - List alternativeIds = new ArrayList<>(); - for (String alternativeIdClaim : alternativeIdClaims) { - Object altId = accessToken.getOtherClaims().get(alternativeIdClaim); - if (!(altId instanceof String)) { - log.trace("Found no value for alternative id claim {}", alternativeIdClaim); - continue; - } - userId = new UserId((String) altId); - user = storage.getUser(userId); - if (user != null) { - log.trace("Successfully mapped subject {} using user id {}", subject, userId); - return new ConqueryAuthenticationInfo(user, token, this, true); - } - } - - throw new UnknownAccountException("The user id was unknown: " + subject); - } - - public static final TokenVerifier.Predicate IS_ACTIVE = new TokenVerifier.Predicate() { - @Override - public boolean test(JsonWebToken t) throws VerificationException { - if (!t.isActive()) { - throw new TokenNotActiveException(t, "Token is not active"); - } - - return true; - } - }; +public class JwtPkceVerifyingRealm extends ConqueryAuthenticationRealm { + + private static final Class TOKEN_CLASS = BearerToken.class; + + Supplier> idpConfigurationSupplier; + private final String[] allowedAudience; + private final TokenVerifier.Predicate[] tokenChecks; + private final List alternativeIdClaims; + private final ActiveWithLeewayVerifier activeVerifier; + + public JwtPkceVerifyingRealm(@NonNull Supplier> idpConfigurationSupplier, @NonNull String allowedAudience, List> additionalTokenChecks, List alternativeIdClaims, int tokenLeeway) { + this.idpConfigurationSupplier = idpConfigurationSupplier; + this.allowedAudience = new String[] {allowedAudience}; + this.tokenChecks = additionalTokenChecks.toArray((TokenVerifier.Predicate[])Array.newInstance(TokenVerifier.Predicate.class,0)); + this.alternativeIdClaims = alternativeIdClaims; + this.setCredentialsMatcher(SkippingCredentialsMatcher.INSTANCE); + this.setAuthenticationTokenClass(TOKEN_CLASS); + this.activeVerifier = new ActiveWithLeewayVerifier(tokenLeeway); + } + + + @Override + protected ConqueryAuthenticationInfo doGetConqueryAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + Optional idpConfigurationOpt = idpConfigurationSupplier.get(); + if (idpConfigurationOpt.isEmpty()) { + log.warn("Unable to start authentication, because idp configuration is not available."); + return null; + } + JwtPkceVerifyingRealmFactory.IdpConfiguration idpConfiguration = idpConfigurationOpt.get(); + + log.trace("Creating token verifier"); + TokenVerifier verifier = TokenVerifier.create(((BearerToken) token).getToken(), AccessToken.class) + .withChecks(new TokenVerifier.RealmUrlCheck(idpConfiguration.getIssuer()), TokenVerifier.SUBJECT_EXISTS_CHECK, activeVerifier) + .withChecks(tokenChecks) + .publicKey(idpConfiguration.getPublicKey()) + .audience(allowedAudience); + + String subject; + log.trace("Verifying token"); + AccessToken accessToken = null; + try { + verifier.verify(); + accessToken = verifier.getToken(); + } catch (VerificationException e) { + log.trace("Verification failed",e); + throw new IncorrectCredentialsException(e); + } + subject = accessToken.getSubject(); + + if (subject == null) { + // Should not happen, as sub is mandatory in an access_token + throw new UnsupportedTokenException("Unable to extract a subject from the provided token."); + } + + log.trace("Authentication successfull for subject {}", subject); + + // Extract alternative ids + List alternativeIds = new ArrayList<>(); + for (String alternativeIdClaim : alternativeIdClaims) { + Object altId = accessToken.getOtherClaims().get(alternativeIdClaim); + if (!(altId instanceof String)) { + log.trace("Found no value for alternative id claim {}", alternativeIdClaim); + continue; + } + alternativeIds.add(new UserId((String) altId)); + } + + return new ConqueryAuthenticationInfo(new UserId(subject), token, this, true, alternativeIds); + } + + public static final TokenVerifier.Predicate IS_ACTIVE = new TokenVerifier.Predicate() { + @Override + public boolean test(JsonWebToken t) throws VerificationException { + if (! t.isActive()) { + throw new TokenNotActiveException(t, "Token is not active"); + } + + return true; + } + }; } diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/util/SinglePrincipalCollection.java b/backend/src/main/java/com/bakdata/conquery/models/auth/util/SinglePrincipalCollection.java new file mode 100644 index 0000000000..c56d777c42 --- /dev/null +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/util/SinglePrincipalCollection.java @@ -0,0 +1,109 @@ +package com.bakdata.conquery.models.auth.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.bakdata.conquery.ConqueryConstants; +import com.bakdata.conquery.models.identifiable.ids.specific.PermissionOwnerId; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.apache.shiro.subject.PrincipalCollection; + +public class SinglePrincipalCollection implements PrincipalCollection { + + private static final long serialVersionUID = -1801050265305362978L; + + private final String realm = ConqueryConstants.AuthenticationUtil.REALM_NAME; + private final List> principal; + + public SinglePrincipalCollection(PermissionOwnerId principal) { + if(principal == null) { + throw new IllegalArgumentException("Principal is not allowed to be null"); + } + this.principal = Arrays.asList(principal); + } + + @JsonCreator + public SinglePrincipalCollection(List> principal) { + if(principal.isEmpty()) { + throw new IllegalArgumentException("Principal is not allowed to be empty"); + } + this.principal = principal; + } + + @Override @JsonIgnore + public Iterator iterator() { + return new Iterator<>() { + private boolean notCalled = true; + @Override + public boolean hasNext() { + boolean ret = notCalled; + notCalled = false; + return ret; + } + + @Override + public Object next() { + return principal.get(0); + } + }; + } + + @Override @JsonIgnore + public Object getPrimaryPrincipal() { + return principal.get(0); + } + + @Override + public T oneByType(Class type) { + if(type.isInstance(principal.get(0))) { + return (T) principal.get(0); + } + return null; + } + + @Override + public Collection byType(Class type) { + if(type.isInstance(principal.get(0))) { + return (Collection) principal; + } + return Collections.emptyList(); + } + + @Override + public List asList() { + return principal; + } + + @Override + public Set asSet() { + return new HashSet<>(principal); + } + + @Override + public Collection fromRealm(String realmName) { + if(realm.equals(realmName)){ + return principal; + } + return Collections.emptyList(); + } + + @Override @JsonIgnore + public Set getRealmNames() { + if(realm != null) { + return new HashSet(Arrays.asList(realm)); + } + return Collections.emptySet(); + } + + @Override @JsonIgnore + public boolean isEmpty() { + return principal.isEmpty(); + } + +} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/util/SubjectPrincipalCollection.java b/backend/src/main/java/com/bakdata/conquery/models/auth/util/SubjectPrincipalCollection.java deleted file mode 100644 index 1b081b9e83..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/util/SubjectPrincipalCollection.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.bakdata.conquery.models.auth.util; - -import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; -import com.bakdata.conquery.models.auth.entities.Subject; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.EqualsAndHashCode; -import lombok.NonNull; -import org.apache.shiro.subject.PrincipalCollection; - -import java.util.*; - -/** - * Principal collection that carries a ready to use {@link Subject} object. - * - * @implNote This class exists mainly to have a better typing for the principal. - */ -@EqualsAndHashCode -public class SubjectPrincipalCollection implements PrincipalCollection { - - private static final long serialVersionUID = -1801050265305362978L; - - private final Subject principal; - private final ConqueryAuthenticationRealm realm; - - public SubjectPrincipalCollection(@NonNull Subject subject, ConqueryAuthenticationRealm realm) { - this.principal = subject; - this.realm = realm; - } - - @Override @JsonIgnore - public Iterator iterator() { - return Collections.singleton(principal).iterator(); - } - - @Override @JsonIgnore - public Subject getPrimaryPrincipal() { - return principal; - } - - @Override - public T oneByType(Class type) { - if(type.isAssignableFrom(principal.getClass())) { - return (T) principal; - } - return null; - } - - @Override - public Collection byType(Class type) { - if(type.isAssignableFrom(principal.getClass())) { - return List.of((T)principal); - } - return Collections.emptyList(); - } - - @Override - public List asList() { - return List.of(principal); - } - - @Override - public Set asSet() { - return Set.of(principal); - } - - @Override - public Collection fromRealm(String realmName) { - if(realm.getName().equals(realmName)){ - return List.of(principal); - } - return Collections.emptyList(); - } - - @Override @JsonIgnore - public Set getRealmNames() { - if(realm != null) { - return Set.of(realm.getName()); - } - return Collections.emptySet(); - } - - @Override @JsonIgnore - public boolean isEmpty() { - return false; - } - -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/auth/web/DefaultAuthFilter.java b/backend/src/main/java/com/bakdata/conquery/models/auth/web/DefaultAuthFilter.java index ed94d8bc7e..5be873ac72 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/auth/web/DefaultAuthFilter.java +++ b/backend/src/main/java/com/bakdata/conquery/models/auth/web/DefaultAuthFilter.java @@ -4,7 +4,7 @@ import com.bakdata.conquery.models.auth.AuthorizationController; import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; import com.bakdata.conquery.models.auth.ConqueryAuthenticator; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.google.common.base.Function; import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.DefaultUnauthorizedHandler; @@ -37,7 +37,7 @@ @PreMatching @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Priority(Priorities.AUTHENTICATION) -public class DefaultAuthFilter extends AuthFilter { +public class DefaultAuthFilter extends AuthFilter { private final Set tokenExtractors = new HashSet<>(); @@ -100,6 +100,9 @@ public void registerTokenExtractor(TokenExtractor extractor){ * extraction process the Token is resubmitted to the realm from the AuthFilter * to the {@link ConqueryAuthenticator} which dispatches it to shiro. * + * @param request + * An incoming request that potentially holds a token for the + * implementing realm. * @return The extracted {@link AuthenticationToken} or null if no * token could be parsed. */ @@ -113,10 +116,13 @@ public static interface TokenExtractor extends Function + * + * @param

+ * the principal */ @Accessors(chain = true) @Setter - private static class Builder extends AuthFilterBuilder { + private static class Builder extends AuthFilterBuilder { @Override protected DefaultAuthFilter newInstance() { @@ -127,7 +133,7 @@ protected DefaultAuthFilter newInstance() { public static DefaultAuthFilter asDropwizardFeature(MetaStorage storage) { Builder builder = new Builder(); DefaultAuthFilter authFilter = builder - .setAuthenticator(new ConqueryAuthenticator()).setUnauthorizedHandler(new DefaultUnauthorizedHandler()) + .setAuthenticator(new ConqueryAuthenticator(storage)).setUnauthorizedHandler(new DefaultUnauthorizedHandler()) .buildAuthFilter(); return authFilter; } diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/FrontendConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/FrontendConfig.java index 61cacdd2e7..ec2ac967dd 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/FrontendConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/FrontendConfig.java @@ -16,7 +16,7 @@ import javax.validation.constraints.NotNull; import com.bakdata.conquery.apiv1.query.concept.specific.external.DateFormat; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.execution.ManagedExecution; import com.bakdata.conquery.models.externalservice.ResultType; @@ -182,7 +182,7 @@ public DateFormat resolveDateFormat(String name) { /** * Try to create a {@link FullIdPrinter} for user if they are allowed. If not allowed to read ids, they will receive a pseudomized result instead. */ - public IdPrinter getIdPrinter(Subject owner, ManagedExecution execution, Namespace namespace) { + public IdPrinter getIdPrinter(User owner, ManagedExecution execution, Namespace namespace) { final int size = (int) ids.stream().filter(ColumnConfig::isPrint).count(); final int pos = IntStream.range(0, getIds().size()) diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/XodusStoreFactory.java b/backend/src/main/java/com/bakdata/conquery/models/config/XodusStoreFactory.java index 7538b0e751..52b000001c 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/XodusStoreFactory.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/XodusStoreFactory.java @@ -478,13 +478,9 @@ private void removeStore(XodusStore store) { } } - removeEnvironment(env); - } - - private void removeEnvironment(Environment env) { log.info("Removed last XodusStore in Environment. Removing Environment as well: {}", env.getLocation()); - final List xodusStore= env.computeInReadonlyTransaction(env::getAllStoreNames); + final List xodusStore= env.computeInReadonlyTransaction(t -> env.getAllStoreNames(t)); if (!xodusStore.isEmpty()){ throw new IllegalStateException("Cannot delete environment, because it still contains these stores:" + xodusStore); diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/ApiTokenRealmFactory.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/ApiTokenRealmFactory.java deleted file mode 100644 index 15502d471b..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/ApiTokenRealmFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.bakdata.conquery.models.config.auth; - -import com.bakdata.conquery.commands.ManagerNode; -import com.bakdata.conquery.io.cps.CPSType; -import com.bakdata.conquery.io.jackson.Jackson; -import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; -import com.bakdata.conquery.models.auth.apitoken.ApiToken; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenCreator; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenRealm; -import com.bakdata.conquery.models.auth.apitoken.TokenStorage; -import com.bakdata.conquery.models.auth.web.DefaultAuthFilter; -import com.bakdata.conquery.models.config.XodusConfig; -import com.bakdata.conquery.resources.api.ApiTokenResource; -import io.dropwizard.jersey.setup.JerseyEnvironment; -import io.dropwizard.util.Strings; -import lombok.Data; -import org.apache.http.util.CharArrayBuffer; -import org.apache.shiro.authc.AuthenticationToken; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.glassfish.hk2.utilities.binding.AbstractBinder; - -import javax.validation.constraints.NotNull; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.HttpHeaders; -import java.nio.file.Path; - -@CPSType(base = AuthenticationRealmFactory.class, id = "API_TOKEN") -@Data -public class ApiTokenRealmFactory implements AuthenticationRealmFactory { - - @NotNull - private final Path storeDir; - - @NotNull - private final XodusConfig apiTokenStoreConfig; - - @Override - public ConqueryAuthenticationRealm createRealm(ManagerNode managerNode) { - - final TokenStorage tokenStorage = new TokenStorage(storeDir, apiTokenStoreConfig, managerNode.getValidator(), Jackson.BINARY_MAPPER.copy()); - managerNode.getEnvironment().lifecycle().manage(tokenStorage); - - final ApiTokenRealm apiTokenRealm = new ApiTokenRealm(managerNode.getStorage(), tokenStorage); - - managerNode.getAuthController().getAuthenticationFilter().registerTokenExtractor(new ApiTokenExtractor()); - - JerseyEnvironment environment = managerNode.getEnvironment().jersey(); - environment.register(new AbstractBinder() { - @Override - protected void configure() { - bind(apiTokenRealm).to(ApiTokenRealm.class); - } - }); - environment.register(ApiTokenResource.class); - - return apiTokenRealm; - } - - public static class ApiTokenExtractor implements DefaultAuthFilter.TokenExtractor { - - @Override - public @Nullable AuthenticationToken apply(@Nullable ContainerRequestContext input) { - if (input == null) { - return null; - } - - // Unfortunately there is no way around the String here - final String authHeader = input.getHeaderString(HttpHeaders.AUTHORIZATION); - - if (Strings.isNullOrEmpty(authHeader)) { - return null; - } - - String[] splits = authHeader.split(" "); - if (splits.length != 2) { - return null; - } - - String token = splits[1]; - - if (!token.startsWith(ApiTokenCreator.TOKEN_PREFIX)) { - return null; - } - - final CharArrayBuffer tokenBuf = new CharArrayBuffer(token.length()); - tokenBuf.append(token); - return new ApiToken(tokenBuf); - } - } - - -} diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthenticationRealmFactory.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthenticationRealmFactory.java index 6bc2490087..aa063bc867 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthenticationRealmFactory.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthenticationRealmFactory.java @@ -5,7 +5,6 @@ import com.bakdata.conquery.models.auth.ConqueryAuthenticationRealm; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.apache.shiro.realm.AuthenticatingRealm; @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") @CPSBase diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthorizationConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthorizationConfig.java index c74ce52c1e..e7358765ce 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthorizationConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/AuthorizationConfig.java @@ -1,14 +1,15 @@ package com.bakdata.conquery.models.config.auth; +import java.util.List; + +import javax.validation.constraints.NotNull; + import com.bakdata.conquery.apiv1.auth.ProtoUser; import com.bakdata.conquery.io.cps.CPSBase; import com.bakdata.conquery.models.auth.UserManageable; import com.bakdata.conquery.models.auth.permissions.ExecutionPermission; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import javax.validation.constraints.NotNull; -import java.util.List; - /** * Configurations of this type define the initial users with their permissions * and optional credentials that might be registered by realm that are @@ -30,5 +31,4 @@ public interface AuthorizationConfig { * Usually {@link ExecutionPermission} are not included, since they add little information to the overview. */ List getOverviewScope(); - } diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java index 68dd789678..d3696844bb 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java @@ -1,12 +1,13 @@ package com.bakdata.conquery.models.config.auth; -import com.bakdata.conquery.apiv1.auth.ProtoUser; -import com.bakdata.conquery.io.cps.CPSType; -import lombok.Getter; +import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; -import java.util.List; + +import com.bakdata.conquery.apiv1.auth.ProtoUser; +import com.bakdata.conquery.io.cps.CPSType; +import lombok.Getter; @CPSType(base = AuthorizationConfig.class, id = "DEFAULT") @Getter @@ -18,4 +19,5 @@ public class DefaultAuthorizationConfig implements AuthorizationConfig { @NotEmpty private List overviewScope; + } diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java index b4eeb5064c..1b39214d14 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java @@ -1,5 +1,11 @@ package com.bakdata.conquery.models.config.auth; +import java.util.List; +import java.util.Set; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + import com.bakdata.conquery.apiv1.auth.ProtoUser; import com.bakdata.conquery.io.cps.CPSType; import com.bakdata.conquery.models.auth.permissions.AdminPermission; @@ -7,11 +13,6 @@ import com.bakdata.conquery.models.auth.permissions.SuperPermission; import lombok.Getter; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Set; - @CPSType(base = AuthorizationConfig.class, id = "DEVELOPMENT") @Getter diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java index 024f142b2d..d614419147 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/JwtPkceVerifyingRealmFactory.java @@ -156,7 +156,7 @@ public ConqueryAuthenticationRealm createRealm(ManagerNode manager) { redirectingAuthFilter.getAuthAttemptCheckers().add(this::checkAndRedeemRefreshToken); redirectingAuthFilter.getLoginInitiators().add(this::initiateLogin); - return new JwtPkceVerifyingRealm(idpConfigurationSupplier, client, additionalVerifiers, alternativeIdClaims, manager.getStorage(), tokenLeeway); + return new JwtPkceVerifyingRealm(idpConfigurationSupplier, client, additionalVerifiers, alternativeIdClaims, tokenLeeway); } @Data diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java index 4646a0b298..7010176b48 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/auth/LocalAuthenticationConfig.java @@ -83,7 +83,8 @@ public ConqueryAuthenticationRealm createRealm(ManagerNode manager) { storeName, directory, passwordStoreConfig, - jwtDuration); + jwtDuration + ); UserAuthenticationManagementProcessor processor = new UserAuthenticationManagementProcessor(realm, manager.getStorage()); // Register resources for users to exchange username and password for an access token diff --git a/backend/src/main/java/com/bakdata/conquery/models/datasets/concepts/FrontEndConceptBuilder.java b/backend/src/main/java/com/bakdata/conquery/models/datasets/concepts/FrontEndConceptBuilder.java index 40148e6528..c63240e678 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/datasets/concepts/FrontEndConceptBuilder.java +++ b/backend/src/main/java/com/bakdata/conquery/models/datasets/concepts/FrontEndConceptBuilder.java @@ -19,7 +19,7 @@ import com.bakdata.conquery.apiv1.frontend.FEValidityDate; import com.bakdata.conquery.apiv1.frontend.FEValue; import com.bakdata.conquery.io.storage.NamespaceStorage; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.datasets.Column; import com.bakdata.conquery.models.datasets.concepts.filters.Filter; @@ -44,7 +44,7 @@ @Slf4j public class FrontEndConceptBuilder { - public static FERoot createRoot(NamespaceStorage storage, Subject subject) { + public static FERoot createRoot(NamespaceStorage storage, User user) { FERoot root = new FERoot(); Map, FENode> roots = root.getConcepts(); @@ -58,7 +58,7 @@ public static FERoot createRoot(NamespaceStorage storage, Subject subject) { } // Submit all permissions to Shiro - boolean[] isPermitted = subject.isPermitted(allConcepts, Ability.READ); + boolean[] isPermitted = user.isPermitted(allConcepts, Ability.READ); for (int i = 0; i maximumSize) { - log.trace("Too many possible values ({} of {} in Filter[{}]). Upgrading to BigMultiSelect", values.size(), maximumSize, getId()); + log.warn("Too many possible values ({} of {} in Filter[{}]). Upgrading to BigMultiSelect", values.size(), maximumSize, getId()); f.setType(FEFilterType.BIG_MULTI_SELECT); } diff --git a/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java b/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java index a1920ec757..1295eb7aab 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java +++ b/backend/src/main/java/com/bakdata/conquery/models/execution/ManagedExecution.java @@ -27,7 +27,6 @@ import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.Group; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.auth.permissions.ExecutionPermission; @@ -240,13 +239,13 @@ public void awaitDone(int time, TimeUnit unit) { Uninterruptibles.awaitUninterruptibly(execution, time, unit); } - public void setStatusBase(@NonNull Subject subject, @NonNull ExecutionStatus status) { + public void setStatusBase(@NonNull User user, @NonNull ExecutionStatus status) { status.setLabel(label == null ? queryId.toString() : getLabelWithoutAutoLabelSuffix()); status.setPristineLabel(label == null || queryId.toString().equals(label) || isAutoLabeled()); status.setId(getId()); status.setTags(tags); status.setShared(shared); - status.setOwn(subject.isOwner(this)); + status.setOwn(user.isOwner(this)); status.setCreatedAt(getCreationTime().atZone(ZoneId.systemDefault())); status.setRequiredTime((startTime != null && finishTime != null) ? ChronoUnit.MILLIS.between(startTime, finishTime) : null); status.setStartTime(startTime); @@ -261,9 +260,9 @@ public void setStatusBase(@NonNull Subject subject, @NonNull ExecutionStatus sta /** * Renders a lightweight status with meta information about this query. Computation an size should be small for this. */ - public OverviewExecutionStatus buildStatusOverview(UriBuilder url, Subject subject) { + public OverviewExecutionStatus buildStatusOverview(UriBuilder url, User user) { OverviewExecutionStatus status = new OverviewExecutionStatus(); - setStatusBase(subject, status); + setStatusBase(user, status); return status; } @@ -272,14 +271,14 @@ public OverviewExecutionStatus buildStatusOverview(UriBuilder url, Subject subje * Renders an extensive status of this query (see {@link FullExecutionStatus}. The rendering can be computation intensive and can produce a large * object. The use of the full status is only intended if a client requested specific information about this execution. */ - public FullExecutionStatus buildStatusFull(@NonNull MetaStorage storage, Subject subject, DatasetRegistry datasetRegistry, ConqueryConfig config) { + public FullExecutionStatus buildStatusFull(@NonNull MetaStorage storage, User user, DatasetRegistry datasetRegistry, ConqueryConfig config) { initExecutable(datasetRegistry, config); FullExecutionStatus status = new FullExecutionStatus(); - setStatusBase(subject, status); + setStatusBase(user, status); - setAdditionalFieldsForStatusWithColumnDescription(storage, subject, status, datasetRegistry); - setAdditionalFieldsForStatusWithSource(subject, status); + setAdditionalFieldsForStatusWithColumnDescription(storage, user, status, datasetRegistry); + setAdditionalFieldsForStatusWithSource(user, status); setAdditionalFieldsForStatusWithGroups(storage, status); setAvailableSecondaryIds(status); status.setProgress(progress); @@ -318,14 +317,14 @@ private void setAdditionalFieldsForStatusWithGroups(@NonNull MetaStorage storage status.setGroups(permittedGroups); } - protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, Subject subject, FullExecutionStatus status, DatasetRegistry datasetRegistry) { + protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, User user, FullExecutionStatus status, DatasetRegistry datasetRegistry) { // Implementation specific } /** * Sets additional fields of an {@link ExecutionStatus} when a more specific status is requested. */ - protected void setAdditionalFieldsForStatusWithSource(Subject subject, FullExecutionStatus status) { + protected void setAdditionalFieldsForStatusWithSource(User user, FullExecutionStatus status) { QueryDescription query = getSubmitted(); NamespacedIdentifiableCollector namespacesIdCollector = new NamespacedIdentifiableCollector(); query.visit(namespacesIdCollector); @@ -337,7 +336,7 @@ protected void setAdditionalFieldsForStatusWithSource(Subject subject, FullExecu .map(ConceptElement::getConcept) .collect(Collectors.toSet()); - boolean canExpand = subject.isPermittedAll(concepts, Ability.READ); + boolean canExpand = user.isPermittedAll(concepts, Ability.READ); status.setCanExpand(canExpand); status.setQuery(canExpand ? getSubmitted() : null); diff --git a/backend/src/main/java/com/bakdata/conquery/models/execution/Shareable.java b/backend/src/main/java/com/bakdata/conquery/models/execution/Shareable.java index da02b632c1..aadb68e39a 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/execution/Shareable.java +++ b/backend/src/main/java/com/bakdata/conquery/models/execution/Shareable.java @@ -8,7 +8,7 @@ import com.bakdata.conquery.apiv1.MetaDataPatch; import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.Group; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.AbilitySets; import com.bakdata.conquery.models.auth.permissions.Authorized; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; @@ -33,7 +33,7 @@ public interface Shareable extends Authorized { default , S extends Identifiable & Shareable & Authorized> Consumer sharer( MetaStorage storage, - Subject subject) { + User user) { if(!(this instanceof Identifiable)) { log.warn("Cannot share {} ({}) because it does not implement Identifiable", this.getClass(), this.toString()); return QueryUtils.getNoOpEntryPoint(); @@ -47,7 +47,7 @@ default , S extends Identifiable & Shareable & continue; } - log.trace("User {} unshares instance {} ({}) from owner {}.", subject, shareable.getClass().getSimpleName(), shareable.getId(), group1); + log.trace("User {} unshares instance {} ({}) from owner {}.", user, shareable.getClass().getSimpleName(), shareable.getId(), group1); group1.removePermission(shareable.createPermission(AbilitySets.SHAREHOLDER)); } @@ -61,7 +61,7 @@ default , S extends Identifiable & Shareable & ConqueryPermission sharePermission = shareable.createPermission(AbilitySets.SHAREHOLDER); group.addPermission(sharePermission); - log.trace("User {} shares instance {} ({}). Adding permission {} to owner {}.", subject, shareable.getClass().getSimpleName(), shareable.getId(), sharePermission, group); + log.trace("User {} shares instance {} ({}). Adding permission {} to owner {}.", user, shareable.getClass().getSimpleName(), shareable.getId(), sharePermission, group); } } diff --git a/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java b/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java index 548bf99fc9..cc90d20659 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java @@ -20,7 +20,6 @@ import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.Group; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.auth.permissions.FormConfigPermission; @@ -93,7 +92,7 @@ public FormConfigId createId() { * Provides an overview (meta data) of this form configuration without the * actual form field values. */ - public FormConfigOverviewRepresentation overview(Subject subject) { + public FormConfigOverviewRepresentation overview(User user) { String ownerName = Optional.ofNullable(owner).map(User::getLabel).orElse(null); return FormConfigOverviewRepresentation.builder() @@ -102,7 +101,7 @@ public FormConfigOverviewRepresentation overview(Subject subject) { .label(label) .tags(tags) .ownerName(ownerName) - .own(subject.isOwner(this)) + .own(user.isOwner(this)) .createdAt(getCreationTime().atZone(ZoneId.systemDefault())) .shared(shared) // system? @@ -112,7 +111,7 @@ public FormConfigOverviewRepresentation overview(Subject subject) { /** * Return the full representation of the configuration with the configured form fields and meta data. */ - public FormConfigFullRepresentation fullRepresentation(MetaStorage storage, Subject requestingUser){ + public FormConfigFullRepresentation fullRepresentation(MetaStorage storage, User requestingUser){ String ownerName = Optional.ofNullable(owner).map(User::getLabel).orElse(null); /* Calculate which groups can see this query. diff --git a/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormConfigProcessor.java b/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormConfigProcessor.java index 2efbaffde4..038ddb6de6 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormConfigProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormConfigProcessor.java @@ -14,7 +14,6 @@ import com.bakdata.conquery.io.jackson.Jackson; import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; import com.bakdata.conquery.models.auth.permissions.FormConfigPermission; @@ -55,22 +54,22 @@ public class FormConfigProcessor { private final static ObjectMapper MAPPER = Jackson.MAPPER.copy().disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, SerializationFeature.WRITE_NULL_MAP_VALUES);; /** - * Return an overview of all form config available to the subject. The selection can be reduced by setting a specific formType. + * Return an overview of all form config available to the user. The selection can be reduced by setting a specific formType. * The provided overview does not contain the configured values for the form, just the meta data. - * @param subject The subject vor which the overview is created. + * @param user The user vor which the overview is created. * @param dataset * @param requestedFormType Optional form type to filter the overview to that specific type. **/ - public Stream getConfigsByFormType(@NonNull Subject subject, Dataset dataset, @NonNull Set requestedFormType) { + public Stream getConfigsByFormType(@NonNull User user, Dataset dataset, @NonNull Set requestedFormType) { if (requestedFormType.isEmpty()) { - // If no specific form type is provided, show all types the subject is permitted to create. - // However if a subject queries for specific form types, we will show all matching regardless whether - // the form config can be used by the subject again. + // If no specific form type is provided, show all types the user is permitted to create. + // However if a user queries for specific form types, we will show all matching regardless whether + // the form config can be used by the user again. Set allowedFormTypes = new HashSet<>(); for (FormType formType : FormScanner.FRONTEND_FORM_CONFIGS.values()) { - if (!subject.isPermitted(formType, Ability.CREATE)) { + if (!user.isPermitted(formType, Ability.CREATE)) { continue; } @@ -84,35 +83,53 @@ public Stream getConfigsByFormType(@NonNull Su Stream stream = storage.getAllFormConfigs().stream() .filter(c -> dataset.equals(c.getDataset())) .filter(c -> formTypesFinal.contains(c.getFormType())) - .filter(c -> subject.isPermitted(c, Ability.READ)); + .filter(c -> user.isPermitted(c, Ability.READ)); - return stream.map(c -> c.overview(subject)); + return stream.map(c -> c.overview(user)); } /** * Returns the full configuration of a configuration (meta data + configured values). * It also tried to convert all {@link NamespacedId}s into the given dataset, so that the frontend can resolve them. */ - public FormConfigFullRepresentation getConfig(Subject subject, FormConfig form) { + public FormConfigFullRepresentation getConfig(User user, FormConfig form) { - subject.authorize(form,Ability.READ); - return form.fullRepresentation(storage, subject); + user.authorize(form,Ability.READ); + return form.fullRepresentation(storage, user); } /** * Adds the provided config to the desired dataset and the datasets that the - * subject has access to (has the READ ability on the Dataset), if the config is + * user has access to (has the READ ability on the Dataset), if the config is * translatable to those. + * @return */ - public FormConfig addConfig(Subject subject, Dataset targetDataset, FormConfigAPI config) { + public FormConfig addConfig(User user, Dataset targetDataset, FormConfigAPI config) { //TODO clear this up final Namespace namespace = datasetRegistry.get(targetDataset.getId()); - subject.authorize(namespace.getDataset(), Ability.READ); + user.authorize(namespace.getDataset(), Ability.READ); - FormConfig internalConfig = FormConfigAPI.intern(config, storage.getUser(subject.getId()), targetDataset); + List translateToDatasets = datasetRegistry.getAllDatasets() + .stream() + .filter(dId -> user.isPermitted(dId, Ability.READ)) + .map(Identifiable::getId) + .collect(Collectors.toList()); + + translateToDatasets.remove(targetDataset); + + return addConfigAndTranslations(user, targetDataset, config); + } + + /** + * Adds the config to the dataset it was submitted under and also to all other datasets it can be translated to. + * This method does not check permissions. + * @return + */ + public FormConfig addConfigAndTranslations(User user, Dataset targetDataset, FormConfigAPI config) { + FormConfig internalConfig = FormConfigAPI.intern(config, user, targetDataset); // Add the config immediately to the submitted dataset addConfigToDataset(internalConfig); @@ -133,20 +150,19 @@ private FormConfigId addConfigToDataset(FormConfig internalConfig) { /** * Applies a patch to a configuration that allows to change its label or tags or even share it. */ - public FormConfigFullRepresentation patchConfig(Subject subject, FormConfig config, FormConfigPatch patch) { + public FormConfigFullRepresentation patchConfig(User user, FormConfig config, FormConfigPatch patch) { - patch.applyTo(config, storage, subject); + patch.applyTo(config, storage, user); storage.updateFormConfig(config); - return config.fullRepresentation(storage, subject); + return config.fullRepresentation(storage, user); } /** * Deletes a configuration from the storage and all permissions, that have this configuration as target. */ - public void deleteConfig(Subject subject, FormConfig config) { - User user = storage.getUser(subject.getId()); + public void deleteConfig(User user, FormConfig config) { user.authorize( config, Ability.DELETE); storage.removeFormConfig(config.getId()); // Delete corresponding permissions (Maybe better to put it into a slow job) diff --git a/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormProcessor.java b/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormProcessor.java index 1a202425aa..41f15d6cf6 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/models/forms/frontendconfiguration/FormProcessor.java @@ -6,17 +6,17 @@ import java.util.Collection; import java.util.List; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.fasterxml.jackson.databind.JsonNode; public class FormProcessor { - public Collection getFormsForUser(Subject subject) { + public Collection getFormsForUser(User user) { List allowedForms = new ArrayList<>(); for (FormType formMapping : FRONTEND_FORM_CONFIGS.values()) { - if (!subject.isPermitted(formMapping, Ability.CREATE)) { + if (!user.isPermitted(formMapping, Ability.CREATE)) { continue; } diff --git a/backend/src/main/java/com/bakdata/conquery/models/forms/managed/ManagedForm.java b/backend/src/main/java/com/bakdata/conquery/models/forms/managed/ManagedForm.java index 1dfbbc6935..006065edfd 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/forms/managed/ManagedForm.java +++ b/backend/src/main/java/com/bakdata/conquery/models/forms/managed/ManagedForm.java @@ -15,7 +15,6 @@ import com.bakdata.conquery.io.jackson.InternalOnly; import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ExecutionState; @@ -178,8 +177,8 @@ public QueryDescription getSubmitted() { @Override - protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, Subject subject, FullExecutionStatus status, DatasetRegistry datasetRegistry) { - super.setAdditionalFieldsForStatusWithColumnDescription(storage, subject, status, datasetRegistry); + protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, User user, FullExecutionStatus status, DatasetRegistry datasetRegistry) { + super.setAdditionalFieldsForStatusWithColumnDescription(storage, user, status, datasetRegistry); // Set the ColumnDescription if the Form only consits of a single subquery if (subQueries == null) { // If subqueries was not set the Execution was not initialized, do it manually diff --git a/backend/src/main/java/com/bakdata/conquery/models/identifiable/ids/specific/UserId.java b/backend/src/main/java/com/bakdata/conquery/models/identifiable/ids/specific/UserId.java index c0750fc49d..dce9062414 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/identifiable/ids/specific/UserId.java +++ b/backend/src/main/java/com/bakdata/conquery/models/identifiable/ids/specific/UserId.java @@ -14,17 +14,17 @@ public class UserId extends PermissionOwnerId { public static final String TYPE = "user"; @Getter - private final String name; + private final String email; - public UserId(String name) { + public UserId(String email) { super(); - this.name = name; + this.email = email; } @Override public void collectComponents(List components) { components.add(TYPE); - components.add(name); + components.add(email); } public enum Parser implements IId.Parser { diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/ManagedQuery.java b/backend/src/main/java/com/bakdata/conquery/models/query/ManagedQuery.java index 6745fa989b..828ce1d23a 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/ManagedQuery.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/ManagedQuery.java @@ -25,7 +25,6 @@ import com.bakdata.conquery.io.cps.CPSType; import com.bakdata.conquery.io.storage.MetaStorage; import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ExecutionState; @@ -136,8 +135,8 @@ public void start() { } @Override - public void setStatusBase(@NonNull Subject subject, @NonNull ExecutionStatus status) { - super.setStatusBase(subject, status); + public void setStatusBase(@NonNull User user, @NonNull ExecutionStatus status) { + super.setStatusBase(user, status); status.setNumberOfResults(lastResultCount); status.setQueryType(query.getClass().getAnnotation(CPSType.class).id()); @@ -148,8 +147,8 @@ public void setStatusBase(@NonNull Subject subject, @NonNull ExecutionStatus sta } @Override - protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, Subject subject, FullExecutionStatus status, DatasetRegistry datasetRegistry) { - super.setAdditionalFieldsForStatusWithColumnDescription(storage, subject, status, datasetRegistry); + protected void setAdditionalFieldsForStatusWithColumnDescription(@NonNull MetaStorage storage, User user, FullExecutionStatus status, DatasetRegistry datasetRegistry) { + super.setAdditionalFieldsForStatusWithColumnDescription(storage, user, status, datasetRegistry); if (columnDescriptions == null) { columnDescriptions = generateColumnDescriptions(datasetRegistry); } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/admin/rest/AdminResource.java b/backend/src/main/java/com/bakdata/conquery/resources/admin/rest/AdminResource.java index 6ad08616ae..487acf263c 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/admin/rest/AdminResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/admin/rest/AdminResource.java @@ -28,7 +28,6 @@ import com.bakdata.conquery.apiv1.FullExecutionStatus; import com.bakdata.conquery.io.jersey.ExtraMimeTypes; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.Subject; import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.web.AuthCookieFilter; import com.bakdata.conquery.models.config.auth.AuthenticationConfig; @@ -58,7 +57,7 @@ public class AdminResource { @Consumes(MediaType.TEXT_PLAIN) @POST @Path("/script") - public String executeScript(@Auth Subject subject, String script) { + public String executeScript(@Auth User user, String script) { return Objects.toString(processor.executeScript(script)); } @@ -70,7 +69,7 @@ public String executeScript(@Auth Subject subject, String script) { @Consumes(MediaType.TEXT_PLAIN) @POST @Path("/script") - public Object executeScriptJson(@Auth Subject subject, String script) { + public Object executeScriptJson(@Auth User user, String script) { return processor.executeScript(script); } @@ -106,17 +105,17 @@ public Response logout(@Context ContainerRequestContext requestContext) { @GET @Path("/queries") - public FullExecutionStatus[] getQueries(@Auth Subject subject, @QueryParam("limit") OptionalLong limit, @QueryParam("since") Optional since) { + public FullExecutionStatus[] getQueries(@Auth User currentUser, @QueryParam("limit") OptionalLong limit, @QueryParam("since") Optional since) { final MetaStorage storage = processor.getStorage(); final DatasetRegistry datasetRegistry = processor.getDatasetRegistry(); return storage.getAllExecutions().stream() .map(t -> { try { - return t.buildStatusFull(storage, subject, datasetRegistry, processor.getConfig()); + return t.buildStatusFull(storage, currentUser, datasetRegistry, processor.getConfig()); } catch (ConqueryError e) { // Initialization of execution probably failed, so we construct a status based on the overview status final FullExecutionStatus fullExecutionStatus = new FullExecutionStatus(); - t.setStatusBase(subject, fullExecutionStatus); + t.setStatusBase(currentUser, fullExecutionStatus); fullExecutionStatus.setStatus(ExecutionState.FAILED); fullExecutionStatus.setError(e); return fullExecutionStatus; diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetsResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/APIResource.java similarity index 86% rename from backend/src/main/java/com/bakdata/conquery/resources/api/DatasetsResource.java rename to backend/src/main/java/com/bakdata/conquery/resources/api/APIResource.java index abb13c33e2..34d12476be 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetsResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/APIResource.java @@ -17,14 +17,15 @@ @Setter @Produces({ExtraMimeTypes.JSON_STRING, ExtraMimeTypes.SMILE_STRING}) @Consumes({ExtraMimeTypes.JSON_STRING, ExtraMimeTypes.SMILE_STRING}) -@Path("/datasets") -public class DatasetsResource extends HAuthorized { +@Path("/") +public class APIResource extends HAuthorized { @Inject protected ConceptsProcessor processor; @GET + @Path("datasets") public List> getDatasets() { - return processor.getDatasets(subject); + return processor.getDatasets(user); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ApiTokenResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ApiTokenResource.java deleted file mode 100644 index 6316444e64..0000000000 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ApiTokenResource.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.bakdata.conquery.resources.api; - -import com.bakdata.conquery.apiv1.auth.ApiTokenDataRepresentation; -import com.bakdata.conquery.models.auth.apitoken.ApiToken; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenRealm; -import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.auth.entities.Subject; -import io.dropwizard.auth.Auth; - -import javax.inject.Inject; -import javax.validation.Valid; -import javax.ws.rs.*; -import javax.ws.rs.core.Response; - -import java.util.List; -import java.util.UUID; - -/** - * Endpoints to create and manage scoped {@link ApiToken}s. - */ -@Path("token") -public class ApiTokenResource { - - public static final String TOKEN = "token"; - @Inject - private ApiTokenRealm realm; - - - @POST - public ApiToken createToken(@Auth Subject subject, @Valid ApiTokenDataRepresentation.Request tokenData){ - - checkRealUser(subject); - - return realm.createApiToken(subject.getUser(), tokenData); - } - - @GET - public List listUserTokens(@Auth Subject subject) { - - checkRealUser(subject); - - return realm.listUserToken(subject); - } - - @DELETE - @Path("{" + TOKEN + "}") - public Response deleteToken(@Auth Subject subject, @PathParam(TOKEN) UUID id) { - - checkRealUser(subject); - - realm.deleteToken(subject, id); - - return Response.ok().build(); - } - - private static void checkRealUser(Subject subject) { - if (!(subject instanceof User)){ - throw new ForbiddenException("Only real users can request API-tokens"); - } - } -} diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptsProcessor.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptsProcessor.java index a5d49e7a0c..367e9aeb25 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptsProcessor.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ConceptsProcessor.java @@ -22,7 +22,7 @@ import com.bakdata.conquery.apiv1.frontend.FERoot; import com.bakdata.conquery.apiv1.frontend.FEValue; import com.bakdata.conquery.io.storage.NamespaceStorage; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.datasets.concepts.Concept; @@ -88,9 +88,9 @@ public List load(Pair, String> filterAndSearch) }); - public FERoot getRoot(NamespaceStorage storage, Subject subject) { + public FERoot getRoot(NamespaceStorage storage, User user) { - return FrontEndConceptBuilder.createRoot(storage, subject); + return FrontEndConceptBuilder.createRoot(storage, user); } public FEList getNode(Concept concept) { @@ -102,10 +102,10 @@ public FEList getNode(Concept concept) { } } - public List> getDatasets(Subject subject) { + public List> getDatasets(User user) { return namespaces.getAllDatasets() .stream() - .filter(d -> subject.isPermitted(d, Ability.READ)) + .filter(d -> user.isPermitted(d, Ability.READ)) .sorted(Comparator.comparing(Dataset::getWeight) .thenComparing(Dataset::getLabel)) .map(d -> new IdLabel<>(d.getId(), d.getLabel())) diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetResource.java index 5dee71d7b2..5b5fdcd388 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetResource.java @@ -25,6 +25,6 @@ public class DatasetResource extends HDatasets { @GET @Path("concepts") public FERoot getRoot() { - return processor.getRoot(getNamespace().getStorage(), subject); + return processor.getRoot(getNamespace().getStorage(), user); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/FormConfigResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/FormConfigResource.java index 004665dc67..add8cc12e5 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/FormConfigResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/FormConfigResource.java @@ -22,7 +22,7 @@ import com.bakdata.conquery.apiv1.FormConfigPatch; import com.bakdata.conquery.apiv1.forms.FormConfigAPI; import com.bakdata.conquery.io.jersey.ExtraMimeTypes; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.forms.configs.FormConfig; import com.bakdata.conquery.models.forms.configs.FormConfig.FormConfigFullRepresentation; @@ -43,31 +43,31 @@ public class FormConfigResource { private FormConfigProcessor processor; @POST - public Response postConfig(@Auth Subject subject, @Valid FormConfigAPI config) { - return Response.ok(new PostResponse(processor.addConfig(subject, dataset, config).getId())).status(Status.CREATED).build(); + public Response postConfig(@Auth User user, @Valid FormConfigAPI config) { + return Response.ok(new PostResponse(processor.addConfig(user, dataset, config).getId())).status(Status.CREATED).build(); } @GET - public Stream getConfigByUserAndType(@Auth Subject subject, @QueryParam("formType") Set formType) { - return processor.getConfigsByFormType(subject, dataset, formType); + public Stream getConfigByUserAndType(@Auth User user, @QueryParam("formType") Set formType) { + return processor.getConfigsByFormType(user, dataset, formType); } @GET @Path("{" + FORM_CONFIG + "}") - public FormConfigFullRepresentation getConfig(@Auth Subject subject, @PathParam(FORM_CONFIG) FormConfig form) { - return processor.getConfig(subject, form); + public FormConfigFullRepresentation getConfig(@Auth User user, @PathParam(FORM_CONFIG) FormConfig form) { + return processor.getConfig(user, form); } @PATCH @Path("{" + FORM_CONFIG + "}") - public FormConfigFullRepresentation patchConfig(@Auth Subject subject, @PathParam(FORM_CONFIG) FormConfig form, FormConfigPatch patch ) { - return processor.patchConfig(subject, form, patch); + public FormConfigFullRepresentation patchConfig(@Auth User user, @PathParam(FORM_CONFIG) FormConfig form, FormConfigPatch patch ) { + return processor.patchConfig(user, form, patch); } @DELETE @Path("{" + FORM_CONFIG + "}") - public Response deleteConfig(@Auth Subject subject, @PathParam(FORM_CONFIG) FormConfig form) { - processor.deleteConfig(subject, form); + public Response deleteConfig(@Auth User user, @PathParam(FORM_CONFIG) FormConfig form) { + processor.deleteConfig(user, form); return Response.ok().build(); } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/FormResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/FormResource.java index e43d31c292..5519d7525e 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/FormResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/FormResource.java @@ -12,7 +12,7 @@ import javax.ws.rs.core.MediaType; import com.bakdata.conquery.apiv1.AdditionalMediaTypes; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.forms.frontendconfiguration.FormProcessor; import com.fasterxml.jackson.databind.JsonNode; import io.dropwizard.auth.Auth; @@ -33,8 +33,8 @@ public FormResource(FormProcessor processor) { @GET @Produces(MediaType.APPLICATION_JSON) - public Collection getFormFEConfigs(@Auth Subject subject) { - return processor.getFormsForUser(subject); + public Collection getFormFEConfigs(@Auth User user) { + return processor.getFormsForUser(user); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/MeResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/MeResource.java index 53d6b1a27a..bbbb1a837e 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/MeResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/MeResource.java @@ -26,6 +26,6 @@ public class MeResource extends HAuthorized { @Produces(MediaType.APPLICATION_JSON) @GET public FEMeInformation getUserInformation() { - return processor.getUserInformation(subject.getUser()); + return processor.getUserInformation(user); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/QueryResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/QueryResource.java index 3e40783987..4f310ad778 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/QueryResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/QueryResource.java @@ -34,7 +34,7 @@ import com.bakdata.conquery.apiv1.query.ExternalUpload; import com.bakdata.conquery.apiv1.query.ExternalUploadResult; import com.bakdata.conquery.apiv1.query.QueryDescription; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.exceptions.JSONException; @@ -59,83 +59,83 @@ public class QueryResource { private Dataset dataset; @GET - public List getAllQueries(@Auth Subject subject, @QueryParam("all-providers") Optional allProviders) { + public List getAllQueries(@Auth User user, @QueryParam("all-providers") Optional allProviders) { - subject.authorize(dataset, Ability.READ); + user.authorize(dataset, Ability.READ); - return processor.getAllQueries(dataset, servletRequest, subject, allProviders.orElse(false)) + return processor.getAllQueries(dataset, servletRequest, user, allProviders.orElse(false)) .collect(Collectors.toList()); } @POST - public Response postQuery(@Auth Subject subject, @QueryParam("all-providers") Optional allProviders, @NotNull @Valid QueryDescription query) { + public Response postQuery(@Auth User user, @QueryParam("all-providers") Optional allProviders, @NotNull @Valid QueryDescription query) { - subject.authorize(dataset, Ability.READ); + user.authorize(dataset, Ability.READ); - ManagedExecution execution = processor.postQuery(dataset, query, subject); + ManagedExecution execution = processor.postQuery(dataset, query, user); - return Response.ok(processor.getQueryFullStatus(execution, subject, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false))) + return Response.ok(processor.getQueryFullStatus(execution, user, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false))) .status(Status.CREATED) .build(); } @GET @Path("{" + QUERY + "}") - public FullExecutionStatus getStatus(@Auth Subject subject, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders) + public FullExecutionStatus getStatus(@Auth User user, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders) throws InterruptedException { - subject.authorize(dataset, Ability.READ); - subject.authorize(query, Ability.READ); + user.authorize(dataset, Ability.READ); + user.authorize(query, Ability.READ); query.awaitDone(1, TimeUnit.SECONDS); - return processor.getQueryFullStatus(query, subject, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); + return processor.getQueryFullStatus(query, user, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); } @PATCH @Path("{" + QUERY + "}") - public FullExecutionStatus patchQuery(@Auth Subject subject, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders, MetaDataPatch patch) + public FullExecutionStatus patchQuery(@Auth User user, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders, MetaDataPatch patch) throws JSONException { - subject.authorize(dataset, Ability.READ); - subject.authorize(query, Ability.READ); + user.authorize(dataset, Ability.READ); + user.authorize(query, Ability.READ); - processor.patchQuery(subject, query, patch); + processor.patchQuery(user, query, patch); - return processor.getQueryFullStatus(query, subject, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); + return processor.getQueryFullStatus(query, user, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); } @DELETE @Path("{" + QUERY + "}") - public void deleteQuery(@Auth Subject subject, @PathParam(QUERY) ManagedExecution query) { - subject.authorize(dataset, Ability.READ); - subject.authorize(query, Ability.DELETE); + public void deleteQuery(@Auth User user, @PathParam(QUERY) ManagedExecution query) { + user.authorize(dataset, Ability.READ); + user.authorize(query, Ability.DELETE); - processor.deleteQuery(subject, query); + processor.deleteQuery(user, query); } @POST @Path("{" + QUERY + "}/reexecute") - public FullExecutionStatus reexecute(@Auth Subject subject, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders) { - subject.authorize(dataset, Ability.READ); - subject.authorize(query, Ability.READ); + public FullExecutionStatus reexecute(@Auth User user, @PathParam(QUERY) ManagedExecution query, @QueryParam("all-providers") Optional allProviders) { + user.authorize(dataset, Ability.READ); + user.authorize(query, Ability.READ); - processor.reexecute(subject, query); - return processor.getQueryFullStatus(query, subject, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); + processor.reexecute(user, query); + return processor.getQueryFullStatus(query, user, RequestAwareUriBuilder.fromRequest(servletRequest), allProviders.orElse(false)); } @POST @Path("{" + QUERY + "}/cancel") - public void cancel(@Auth Subject subject, @PathParam(QUERY) ManagedExecution query) { + public void cancel(@Auth User user, @PathParam(QUERY) ManagedExecution query) { - subject.authorize(dataset, Ability.READ); - subject.authorize(query, Ability.CANCEL); + user.authorize(dataset, Ability.READ); + user.authorize(query, Ability.CANCEL); - processor.cancel(subject, dataset, query); + processor.cancel(user, dataset, query); } @POST @Path("/upload") - public ExternalUploadResult upload(@Auth Subject subject, @Valid ExternalUpload upload) { - return processor.uploadEntities(subject, dataset, upload); + public ExternalUploadResult upload(@Auth User user, @Valid ExternalUpload upload) { + return processor.uploadEntities(user, dataset, upload); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowFileResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowFileResource.java index c01f2c6bff..9871753114 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowFileResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowFileResource.java @@ -18,7 +18,7 @@ import com.bakdata.conquery.apiv1.AdditionalMediaTypes; import com.bakdata.conquery.io.result.arrow.ResultArrowFileProcessor; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ManagedExecution; import com.bakdata.conquery.models.query.SingleTableResult; @@ -37,14 +37,14 @@ public class ResultArrowFileResource { @Path("{" + QUERY + "}." + FILE_EXTENTION_ARROW_FILE) @Produces(AdditionalMediaTypes.ARROW_FILE) public Response get( - @Auth Subject subject, + @Auth User user, @PathParam(DATASET) Dataset dataset, @PathParam(QUERY) ManagedExecution query, @QueryParam("pretty") Optional pretty) { checkSingleTableResult(query); - log.info("Result for {} download on dataset {} by subject {} ({}).", query.getId(), dataset.getId(), subject.getId(), subject.getName()); - return processor.getArrowFileResult(subject, (ManagedExecution & SingleTableResult) query, dataset, pretty.orElse(false)); + log.info("Result for {} download on dataset {} by user {} ({}).", query.getId(), dataset.getId(), user.getId(), user.getName()); + return processor.getArrowFileResult(user, (ManagedExecution & SingleTableResult) query, dataset, pretty.orElse(false)); } public static & SingleTableResult> URL getDownloadURL(UriBuilder uriBuilder, E exec) throws MalformedURLException { diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowStreamResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowStreamResource.java index ba0641891e..4dc0deb93d 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowStreamResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultArrowStreamResource.java @@ -18,7 +18,7 @@ import com.bakdata.conquery.apiv1.AdditionalMediaTypes; import com.bakdata.conquery.io.result.arrow.ResultArrowStreamProcessor; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ManagedExecution; import com.bakdata.conquery.models.query.SingleTableResult; @@ -49,13 +49,13 @@ public static URL getDownloadURL(UriBuilder uriBuilder, ManagedExecution exec @Path("{" + QUERY + "}." + FILE_EXTENTION_ARROW_STREAM) @Produces(AdditionalMediaTypes.ARROW_STREAM) public Response get( - @Auth Subject subject, + @Auth User user, @PathParam(DATASET) Dataset dataset, @PathParam(QUERY) ManagedExecution execution, @QueryParam("pretty") Optional pretty) { checkSingleTableResult(execution); - log.info("Result for {} download on dataset {} by subject {} ({}).", execution, dataset, subject.getId(), subject.getName()); - return processor.getArrowStreamResult(subject, (ManagedExecution & SingleTableResult)execution, dataset, pretty.orElse(false)); + log.info("Result for {} download on dataset {} by user {} ({}).", execution, dataset, user.getId(), user.getName()); + return processor.getArrowStreamResult(user, (ManagedExecution & SingleTableResult)execution, dataset, pretty.orElse(false)); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultCsvResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultCsvResource.java index e8a57f4eb8..98c2d9f7b5 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultCsvResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultCsvResource.java @@ -20,7 +20,7 @@ import com.bakdata.conquery.apiv1.AdditionalMediaTypes; import com.bakdata.conquery.io.result.csv.ResultCsvProcessor; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; import com.bakdata.conquery.models.execution.ManagedExecution; @@ -53,15 +53,15 @@ public static & SingleTableResult> URL getDownloa @Path("{" + QUERY + "}.csv") @Produces(AdditionalMediaTypes.CSV) public Response getAsCsv( - @Auth Subject subject, + @Auth User user, @PathParam(DATASET) Dataset datasetId, @PathParam(QUERY) ManagedExecution execution, - @HeaderParam("subject-agent") String userAgent, + @HeaderParam("user-agent") String userAgent, @QueryParam("charset") String queryCharset, @QueryParam("pretty") Optional pretty) { checkSingleTableResult(execution); - log.info("Result for {} download on dataset {} by subject {} ({}).", execution, datasetId, subject.getId(), subject.getName()); - return processor.getResult(subject, datasetId, (ManagedExecution & SingleTableResult) execution, userAgent, queryCharset, pretty.orElse(Boolean.TRUE)); + log.info("Result for {} download on dataset {} by user {} ({}).", execution, datasetId, user.getId(), user.getName()); + return processor.getResult(user, datasetId, (ManagedExecution & SingleTableResult) execution, userAgent, queryCharset, pretty.orElse(Boolean.TRUE)); } } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultExcelResource.java b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultExcelResource.java index 73a8a25f23..17d208ea34 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/api/ResultExcelResource.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/api/ResultExcelResource.java @@ -19,7 +19,7 @@ import com.bakdata.conquery.apiv1.AdditionalMediaTypes; import com.bakdata.conquery.io.result.excel.ResultExcelProcessor; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.execution.ManagedExecution; import com.bakdata.conquery.models.identifiable.ids.specific.DatasetId; import com.bakdata.conquery.models.query.SingleTableResult; @@ -40,13 +40,13 @@ public class ResultExcelResource { @Path("{" + QUERY + "}.xlsx") @Produces(AdditionalMediaTypes.ARROW_FILE) public Response get( - @Auth Subject subject, + @Auth User user, @PathParam(DATASET) DatasetId datasetId, @PathParam(QUERY) ManagedExecution execution, @QueryParam("pretty") Optional pretty) { checkSingleTableResult(execution); - log.info("Result for {} download on dataset {} by subject {} ({}).", execution.getId(), datasetId, subject.getId(), subject.getName()); - return processor.getExcelResult(subject, (ManagedExecution & SingleTableResult) execution, datasetId, pretty.orElse(true)); + log.info("Result for {} download on dataset {} by user {} ({}).", execution.getId(), datasetId, user.getId(), user.getName()); + return processor.getExcelResult(user, (ManagedExecution & SingleTableResult) execution, datasetId, pretty.orElse(true)); } public static & SingleTableResult> URL getDownloadURL(UriBuilder uriBuilder, E exec) throws MalformedURLException { diff --git a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAdmin.java b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAdmin.java index 12b158fef7..ea8d04779c 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAdmin.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAdmin.java @@ -1,12 +1,16 @@ package com.bakdata.conquery.resources.hierarchies; import javax.annotation.PostConstruct; +import javax.inject.Inject; +import com.bakdata.conquery.models.auth.AuthorizationHelper; import com.bakdata.conquery.models.auth.permissions.Ability; import com.bakdata.conquery.models.auth.permissions.AdminPermission; import com.bakdata.conquery.models.auth.permissions.Authorized; import com.bakdata.conquery.models.auth.permissions.ConqueryPermission; +import com.bakdata.conquery.resources.admin.rest.AdminProcessor; +import java.util.Collections; import java.util.Set; /** @@ -21,7 +25,7 @@ public abstract class HAdmin extends HAuthorized implements Authorized { public void init() { super.init(); - subject.authorize(this, Ability.READ); + user.authorize(this, Ability.READ); } @Override diff --git a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAuthorized.java b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAuthorized.java index 312003c766..af32bc7d38 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAuthorized.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HAuthorized.java @@ -8,7 +8,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.Response.Status; -import com.bakdata.conquery.models.auth.entities.Subject; +import com.bakdata.conquery.models.auth.entities.User; import lombok.Getter; import lombok.Setter; import org.glassfish.jersey.server.ContainerRequest; @@ -17,7 +17,7 @@ @Setter public abstract class HAuthorized { - protected Subject subject; + protected User user; @Context protected ContainerRequest request; @Context @@ -29,8 +29,8 @@ public void init() { * We need to extract the user ourself here because @Auth does not work anymore on fields. * See https://github.com/dropwizard/dropwizard/issues/3407 */ - subject = provide(); - if(subject == null) { + user = provide(); + if(user == null) { throw new WebApplicationException(Status.UNAUTHORIZED); } } @@ -39,8 +39,8 @@ public void init() { * @return {@link Principal} stored on the request, or {@code null} * if no object was found. */ - public Subject provide() { - final Subject principal = (Subject) request.getSecurityContext().getUserPrincipal(); + public User provide() { + final User principal = (User) request.getSecurityContext().getUserPrincipal(); if (principal == null) { throw new IllegalStateException("Cannot inject a custom principal into unauthenticated request"); } diff --git a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HConcepts.java b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HConcepts.java index b807b507bb..e34ae9971e 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HConcepts.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HConcepts.java @@ -27,6 +27,6 @@ public abstract class HConcepts extends HDatasets { @Override public void init() { super.init(); - subject.authorize(concept, Ability.READ); + user.authorize(concept, Ability.READ); } } \ No newline at end of file diff --git a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HDatasets.java b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HDatasets.java index 0bff459b4e..68a76f0c6b 100644 --- a/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HDatasets.java +++ b/backend/src/main/java/com/bakdata/conquery/resources/hierarchies/HDatasets.java @@ -32,6 +32,6 @@ public abstract class HDatasets extends HAuthorized { public void init() { super.init(); this.namespace = datasetRegistry.get(dataset.getId()); - subject.authorize(dataset, Ability.READ); + user.authorize(dataset, Ability.READ); } } diff --git a/backend/src/test/java/com/bakdata/conquery/api/form/config/FormConfigTest.java b/backend/src/test/java/com/bakdata/conquery/api/form/config/FormConfigTest.java index 25fb431fe8..bd76bc3176 100644 --- a/backend/src/test/java/com/bakdata/conquery/api/form/config/FormConfigTest.java +++ b/backend/src/test/java/com/bakdata/conquery/api/form/config/FormConfigTest.java @@ -87,7 +87,7 @@ public class FormConfigTest { private DatasetId datasetId1; private ExportForm form; private User user; - + @BeforeAll public void setupTestClass() throws Exception{ diff --git a/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTest.java b/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTest.java index 29b01ade43..aec0c6e5f3 100644 --- a/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTest.java +++ b/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTest.java @@ -8,15 +8,13 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.function.Executable; -import java.io.File; - public interface IntegrationTest { void execute(String name, TestConquery testConquery) throws Exception; - public default void overrideConfig(final ConqueryConfig conf, final File workdir){ + public default void overrideConfig(ConqueryConfig conf){ } diff --git a/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTests.java b/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTests.java index 4fbb3deb72..8bb624870a 100644 --- a/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTests.java +++ b/backend/src/test/java/com/bakdata/conquery/integration/IntegrationTests.java @@ -118,7 +118,7 @@ public Stream programmaticTests() { } private DynamicTest createDynamicProgrammaticTestNode(ProgrammaticIntegrationTest test) { - TestConquery conquery = getCachedConqueryInstance(workDir, getConfigOverride(test, workDir)); + TestConquery conquery = getCachedConqueryInstance(workDir, getConfigOverride(test)); return DynamicTest.dynamicTest( test.getClass().getSimpleName(), @@ -152,7 +152,7 @@ private DynamicNode collectTests(ResourceTree currentDir) { private static DynamicTest readTest(Resource resource, String name, IntegrationTests integrationTests) { try (InputStream in = resource.open()) { JsonIntegrationTest test = new JsonIntegrationTest(in); - ConqueryConfig conf = getConfigOverride(test, integrationTests.getWorkDir()); + ConqueryConfig conf = getConfigOverride(test); name = test.getTestSpec().getLabel(); @@ -180,9 +180,9 @@ private static DynamicTest readTest(Resource resource, String name, IntegrationT } @NotNull - private static ConqueryConfig getConfigOverride(IntegrationTest test, File workDir) { + private static ConqueryConfig getConfigOverride(IntegrationTest test) { ConqueryConfig conf = Cloner.clone(DEFAULT_CONFIG, Map.of(), MAPPER); - test.overrideConfig(conf, workDir); + test.overrideConfig(conf); return conf; } diff --git a/backend/src/test/java/com/bakdata/conquery/integration/json/JsonIntegrationTest.java b/backend/src/test/java/com/bakdata/conquery/integration/json/JsonIntegrationTest.java index 4b46ac0169..e542ce5270 100644 --- a/backend/src/test/java/com/bakdata/conquery/integration/json/JsonIntegrationTest.java +++ b/backend/src/test/java/com/bakdata/conquery/integration/json/JsonIntegrationTest.java @@ -1,6 +1,5 @@ package com.bakdata.conquery.integration.json; -import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -36,7 +35,7 @@ public JsonIntegrationTest(InputStream in) throws IOException { } @Override - public void overrideConfig(final ConqueryConfig conf, final File workDir) { + public void overrideConfig(ConqueryConfig conf) { testSpec.overrideConfig(conf); } diff --git a/backend/src/test/java/com/bakdata/conquery/integration/tests/ApiTokenRealmTest.java b/backend/src/test/java/com/bakdata/conquery/integration/tests/ApiTokenRealmTest.java deleted file mode 100644 index d9c0adf853..0000000000 --- a/backend/src/test/java/com/bakdata/conquery/integration/tests/ApiTokenRealmTest.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.bakdata.conquery.integration.tests; - -import com.bakdata.conquery.apiv1.IdLabel; -import com.bakdata.conquery.apiv1.auth.ApiTokenDataRepresentation; -import com.bakdata.conquery.integration.IntegrationTest; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.apitoken.ApiToken; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenRealm; -import com.bakdata.conquery.models.auth.apitoken.Scopes; -import com.bakdata.conquery.models.auth.conquerytoken.ConqueryTokenRealm; -import com.bakdata.conquery.models.auth.entities.User; -import com.bakdata.conquery.models.config.ConqueryConfig; -import com.bakdata.conquery.models.config.XodusConfig; -import com.bakdata.conquery.models.config.XodusStoreFactory; -import com.bakdata.conquery.models.config.auth.ApiTokenRealmFactory; -import com.bakdata.conquery.models.identifiable.ids.specific.DatasetId; -import com.bakdata.conquery.resources.admin.rest.AdminDatasetsResource; -import com.bakdata.conquery.resources.api.ApiTokenResource; -import com.bakdata.conquery.resources.api.DatasetsResource; -import com.bakdata.conquery.resources.hierarchies.HierarchyHelper; -import com.bakdata.conquery.util.support.StandaloneSupport; -import com.google.common.collect.MoreCollectors; - -import javax.ws.rs.ClientErrorException; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.io.File; -import java.nio.file.Path; -import java.time.LocalDate; -import java.time.temporal.ChronoUnit; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class ApiTokenRealmTest extends IntegrationTest.Simple implements ProgrammaticIntegrationTest { - - @Override - public void overrideConfig(final ConqueryConfig conf, final File workDir) { - - XodusStoreFactory storageConfig = (XodusStoreFactory) conf.getStorage(); - final Path storageDir = workDir.toPath().resolve(storageConfig.getDirectory().resolve(this.getClass().getSimpleName())); - storageConfig.setDirectory(storageDir); - conf.getAuthenticationRealms().add(new ApiTokenRealmFactory(storageDir,new XodusConfig())); - } - - @Override - public void execute(StandaloneSupport conquery) throws Exception { - final User testUser = conquery.getTestUser(); - ApiTokenRealm realm = conquery.getAuthorizationController().getAuthenticationRealms().stream().filter(ApiTokenRealm.class::isInstance).map(ApiTokenRealm.class::cast).collect(MoreCollectors.onlyElement()); - - final ConqueryTokenRealm conqueryTokenRealm = conquery.getAuthorizationController().getConqueryTokenRealm(); - final String userToken = conqueryTokenRealm.createTokenForUser(testUser.getId()); - - // Request ApiToken - final ApiTokenDataRepresentation.Request tokenRequest1 = new ApiTokenDataRepresentation.Request(); - - tokenRequest1.setName("test-token"); - tokenRequest1.setScopes(EnumSet.of(Scopes.DATASET)); - tokenRequest1.setExpirationDate(LocalDate.now().plus(1, ChronoUnit.DAYS)); - - ApiToken apiToken1 = requestApiToken(conquery, userToken, tokenRequest1); - - assertThat(apiToken1.getToken()).isNotBlank(); - - // List ApiToken - List apiTokens = - conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class,"listUserTokens")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + userToken) - .get(new GenericType>(){}); - - final ApiTokenDataRepresentation.Response expected = new ApiTokenDataRepresentation.Response(); - expected.setLastUsed(null); - expected.setCreationDate(LocalDate.now()); - expected.setExpirationDate(LocalDate.now().plus(1, ChronoUnit.DAYS)); - expected.setScopes(EnumSet.of(Scopes.DATASET)); - expected.setName("test-token"); - - assertThat(apiTokens).hasSize(1); - assertThat(apiTokens.get(0)).usingRecursiveComparison().ignoringFields("id").isEqualTo(expected); - - - // Request ApiToken 2 - final ApiTokenDataRepresentation.Request tokenRequest2 = new ApiTokenDataRepresentation.Request(); - - tokenRequest2.setName("test-token"); - tokenRequest2.setScopes(EnumSet.of(Scopes.ADMIN)); - tokenRequest2.setExpirationDate(LocalDate.now().plus(1, ChronoUnit.DAYS)); - - ApiToken apiToken2 = requestApiToken(conquery, userToken, tokenRequest2); - - assertThat(apiToken2.getToken()).isNotBlank(); - - // List ApiToken 2 - apiTokens = requestTokenList(conquery, userToken); - - assertThat(apiTokens).hasSize(2); - - // Use ApiToken1 to get Datasets - List> datasets = requestDatasets(conquery, apiToken1); - - assertThat(datasets).isNotEmpty(); - - // Use ApiToken2 to get Datasets - datasets = requestDatasets(conquery, apiToken2); - - assertThat(datasets).as("The second token has no scope for dataset").isEmpty(); - - - // Use ApiToken2 to access Admin - List adminDatasets = - conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultAdminURIBuilder(), AdminDatasetsResource.class,"listDatasets")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + apiToken2.getToken()) - .get(new GenericType>(){}); - - assertThat(adminDatasets).as("The second token has scope for admin").isNotEmpty(); - - // Try to delete ApiToken2 with ApiToken (should fail) - final UUID id2 = apiTokens.stream().filter(t -> t.getScopes().contains(Scopes.ADMIN)).map(ApiTokenDataRepresentation.Response::getId).collect(MoreCollectors.onlyElement()); - Response response = - conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class,"deleteToken")) - .resolveTemplate(ApiTokenResource.TOKEN, id2) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + apiToken2.getToken()) - .delete(Response.class); - - assertThat(response.getStatus()).as("It is forbidden to act on ApiTokens with ApiTokens").isEqualTo(403); - - - // Delete ApiToken2 with user token - response = - conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class,"deleteToken")) - .resolveTemplate(ApiTokenResource.TOKEN, id2) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + userToken) - .delete(Response.class); - - assertThat(response.getStatus()).as("It is okay to act on ApiTokens with UserTokens").isEqualTo(200); - assertThat(realm.listUserToken(testUser)).hasSize(1); - - // Try to use the deleted token to access Admin - response = conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultAdminURIBuilder(), AdminDatasetsResource.class,"listDatasets")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + apiToken2.getToken()) - .get(Response.class); - - assertThat(response.getStatus()).as("Cannot use deleted token").isEqualTo(401); - - // Try to act on tokens from another user - final MetaStorage metaStorage = conquery.getMetaStorage(); - final User user2 = new User("TestUser2", "TestUser2", metaStorage); - metaStorage.addUser(user2); - final String user2Token = conqueryTokenRealm.createTokenForUser(user2.getId()); - - // Try to delete ApiToken2 with ApiToken (should fail) - final UUID id1 = apiTokens.stream().filter(t -> t.getScopes().contains(Scopes.DATASET)).map(ApiTokenDataRepresentation.Response::getId).collect(MoreCollectors.onlyElement()); - response = - conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class,"deleteToken")) - .resolveTemplate(ApiTokenResource.TOKEN, id1) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + user2Token) - .delete(Response.class); - - assertThat(response.getStatus()).as("It is forbidden to act on someone else ApiTokens").isEqualTo(403); - - // Request ApiToken 3 (expired) - final ApiTokenDataRepresentation.Request tokenRequest3 = new ApiTokenDataRepresentation.Request(); - - tokenRequest3.setName("test-token"); - tokenRequest3.setScopes(EnumSet.of(Scopes.DATASET)); - tokenRequest3.setExpirationDate(LocalDate.now().minus(1, ChronoUnit.DAYS)); - - assertThatThrownBy(() -> requestApiToken(conquery, userToken, tokenRequest3)).as("Expiration date is in the past").isExactlyInstanceOf(ClientErrorException.class).hasMessageContaining("HTTP 422"); - - // Craft expired token behind validation to simulate the use of an expired token - ApiToken apiToken3 = realm.createApiToken(user2, tokenRequest3); - - assertThatThrownBy(() -> requestDatasets(conquery,apiToken3)).as("Expired token").isExactlyInstanceOf(NotAuthorizedException.class); - } - - private List> requestDatasets(StandaloneSupport conquery, ApiToken apiToken) { - return conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), DatasetsResource.class, "getDatasets")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + apiToken.getToken()) - .get(new GenericType<>() { - }); - } - - private List requestTokenList(StandaloneSupport conquery, String userToken) { - return conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class, "listUserTokens")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + userToken) - .get(new GenericType<>() {}); - } - - private ApiToken requestApiToken(StandaloneSupport conquery, String userToken, ApiTokenDataRepresentation.Request tokenRequest) { - return conquery.getClient() - .target(HierarchyHelper.hierarchicalPath(conquery.defaultApiURIBuilder(), ApiTokenResource.class, "createToken")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + userToken) - .post(Entity.entity(tokenRequest, MediaType.APPLICATION_JSON_TYPE), ApiToken.class); - } -} diff --git a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java index e8c0f88548..79c217c98a 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java +++ b/backend/src/test/java/com/bakdata/conquery/models/SerializationTests.java @@ -2,11 +2,8 @@ import java.io.IOException; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Collections; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.UUID; @@ -28,10 +25,6 @@ import com.bakdata.conquery.io.jackson.MutableInjectableValues; import com.bakdata.conquery.io.jackson.serializer.SerializationTestUtil; import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.apitoken.ApiToken; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenCreator; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenData; -import com.bakdata.conquery.models.auth.apitoken.Scopes; import com.bakdata.conquery.models.auth.entities.Group; import com.bakdata.conquery.models.auth.entities.Role; import com.bakdata.conquery.models.auth.entities.User; @@ -59,7 +52,6 @@ import com.bakdata.conquery.models.identifiable.ids.specific.DatasetId; import com.bakdata.conquery.models.identifiable.ids.specific.GroupId; import com.bakdata.conquery.models.identifiable.ids.specific.ManagedExecutionId; -import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import com.bakdata.conquery.models.identifiable.mapping.EntityIdMap; import com.bakdata.conquery.models.query.ManagedQuery; import com.bakdata.conquery.models.query.entity.Entity; @@ -68,8 +60,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.dropwizard.jersey.validation.Validators; import lombok.extern.slf4j.Slf4j; -import org.apache.http.util.CharArrayBuffer; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @Slf4j @@ -400,26 +390,4 @@ public void testFormQuery() throws IOException, JSONException { SerializationTestUtil.forType(AbsoluteFormQuery.class).registry(centralRegistry).test(query); } - @Test - public void testApiTokenData() throws JSONException, IOException { - final CharArrayBuffer buffer = new CharArrayBuffer(5); - buffer.append("testtest"); - final ApiToken apiToken = new ApiToken(buffer); - final ApiTokenData - apiTokenData = - new ApiTokenData( - UUID.randomUUID(), - ApiTokenCreator.hashToken(apiToken), - "tokenName", - new UserId("tokenUser"), - LocalDate.now(), - LocalDate.now().plus(1, ChronoUnit.DAYS), - EnumSet.of(Scopes.DATASET), - STORAGE - ); - - - SerializationTestUtil.forType(ApiTokenData.class).injectables(STORAGE).test(apiTokenData); - } - } diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/ApiTokenTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/ApiTokenTest.java deleted file mode 100644 index 5c923e1da5..0000000000 --- a/backend/src/test/java/com/bakdata/conquery/models/auth/ApiTokenTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.bakdata.conquery.models.auth; - -import static com.bakdata.conquery.models.auth.apitoken.ApiTokenCreator.*; -import static org.assertj.core.api.Assertions.*; - -import com.bakdata.conquery.models.auth.apitoken.ApiToken; -import com.bakdata.conquery.models.auth.apitoken.ApiTokenCreator; -import lombok.extern.slf4j.Slf4j; -import org.apache.http.util.CharArrayBuffer; -import org.junit.jupiter.api.Test; - -import java.util.Random; - -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - -@Slf4j -public class ApiTokenTest { - - @Test - public void checkToken () { - final ApiTokenCreator apiTokenCreator = new ApiTokenCreator(new Random(1)); - - final @NotNull @NotEmpty CharArrayBuffer buffer = apiTokenCreator.createToken().getToken(); - - log.info("Testing token: {}", buffer); - - assertThat(buffer).hasSize(TOKEN_LENGTH + TOKEN_PREFIX.length() + 1); - - assertThat(buffer).matches(TOKEN_PREFIX + "_" + "[\\w\\d_]{"+ TOKEN_LENGTH +"}"); - - assertThat(buffer.toString().substring(TOKEN_PREFIX.length()+2)).containsPattern("[a-zA-Z]"); - } -} diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/IntrospectionDelegatingRealmTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/IntrospectionDelegatingRealmTest.java index d983cc0bc2..7be6dec60b 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/auth/IntrospectionDelegatingRealmTest.java +++ b/backend/src/test/java/com/bakdata/conquery/models/auth/IntrospectionDelegatingRealmTest.java @@ -44,21 +44,18 @@ public class IntrospectionDelegatingRealmTest { // User 1 private static final String USER_1_NAME = "test_name1"; - private static User USER_1 = new User(USER_1_NAME, USER_1_NAME, STORAGE); private static final String USER_1_PASSWORD = "test_password1"; private static final String USER_1_TOKEN = JWT.create().withClaim("name", USER_1_NAME).sign(Algorithm.HMAC256("secret"));; private static final BearerToken USER1_TOKEN_WRAPPED = new BearerToken(USER_1_TOKEN); // User 2 private static final String USER_2_NAME = "test_name2"; - private static User USER_2 = new User(USER_2_NAME, USER_2_NAME, STORAGE); private static final String USER_2_LABEL = "test_label2"; private static final String USER_2_TOKEN = JWT.create().withClaim("name", USER_2_NAME).sign(Algorithm.HMAC256("secret"));; private static final BearerToken USER_2_TOKEN_WRAPPED = new BearerToken(USER_2_TOKEN); // User 3 existing private static final String USER_3_NAME = "test_name3"; - private static User USER_3 = new User(USER_3_NAME, USER_3_NAME, STORAGE); private static final String USER_3_LABEL = "test_label3"; private static final String USER_3_TOKEN = JWT.create().withClaim("name", USER_3_NAME).sign(Algorithm.HMAC256("secret"));; private static final BearerToken USER_3_TOKEN_WRAPPED = new BearerToken(USER_3_TOKEN); @@ -72,7 +69,9 @@ public class IntrospectionDelegatingRealmTest { @BeforeAll public static void beforeAll() { initRealmConfig(); + initOIDCServer(); + } @BeforeEach @@ -158,34 +157,33 @@ public void tokenIntrospectionSimpleUserNew() { assertThat(info) .usingRecursiveComparison() .ignoringFields(ConqueryAuthenticationInfo.Fields.credentials) - .isEqualTo(new ConqueryAuthenticationInfo(USER_1, USER1_TOKEN_WRAPPED, REALM, true)); + .isEqualTo(new ConqueryAuthenticationInfo(new UserId(USER_1_NAME), USER1_TOKEN_WRAPPED, REALM, true)); assertThat(STORAGE.getAllUsers()).containsOnly(new User(USER_1_NAME, USER_1_NAME, STORAGE)); } @Test public void tokenIntrospectionSimpleUserExisting() { - STORAGE.addUser(USER_1); + User existingUser = new User(USER_1_NAME, USER_1_NAME, STORAGE); + STORAGE.addUser(existingUser); AuthenticationInfo info = REALM.doGetAuthenticationInfo(USER1_TOKEN_WRAPPED); assertThat(info) .usingRecursiveComparison() .ignoringFields(ConqueryAuthenticationInfo.Fields.credentials) - .isEqualTo(new ConqueryAuthenticationInfo(USER_1, USER1_TOKEN_WRAPPED, REALM, true)); - assertThat(STORAGE.getAllUsers()).containsOnly(USER_1); + .isEqualTo(new ConqueryAuthenticationInfo(new UserId(USER_1_NAME), USER1_TOKEN_WRAPPED, REALM, true)); + assertThat(STORAGE.getAllUsers()).containsOnly(existingUser); } @Test public void tokenIntrospectionGroupedUser() { - STORAGE.addUser(USER_2); - AuthenticationInfo info = REALM.doGetAuthenticationInfo(USER_2_TOKEN_WRAPPED); - - final ConqueryAuthenticationInfo expected = new ConqueryAuthenticationInfo(USER_2, USER_2_TOKEN_WRAPPED, REALM, true); + assertThat(info) .usingRecursiveComparison() - .isEqualTo(expected); - assertThat(STORAGE.getAllUsers()).containsOnly(USER_2); + .ignoringFields(ConqueryAuthenticationInfo.Fields.credentials) + .isEqualTo(new ConqueryAuthenticationInfo(new UserId(USER_2_NAME), USER_2_TOKEN_WRAPPED, REALM, true)); + assertThat(STORAGE.getAllUsers()).containsOnly(new User(USER_2_NAME, USER_2_LABEL, STORAGE)); assertThat(STORAGE.getAllGroups()).hasSize(2); // Pre-existing group and a second group that has been added in the process assertThat(STORAGE.getGroup(new GroupId(GROUPNAME_1)).getMembers()).contains(new UserId(USER_2_NAME)); assertThat(STORAGE.getGroup(new GroupId(GROUPNAME_2)).getMembers()).contains(new UserId(USER_2_NAME)); @@ -193,8 +191,9 @@ public void tokenIntrospectionGroupedUser() { @Test public void tokenIntrospectionGroupedUserRemoveGroupMapping() { - STORAGE.addUser(USER_3); - GROUP_1_EXISTING.addMember(USER_3); + User user = new User(USER_3_NAME, USER_3_LABEL, STORAGE); + STORAGE.addUser(user); + GROUP_1_EXISTING.addMember(user); assertThat(STORAGE.getGroup(new GroupId(GROUPNAME_1)).getMembers()).contains(new UserId(USER_3_NAME)); @@ -203,8 +202,8 @@ public void tokenIntrospectionGroupedUserRemoveGroupMapping() { assertThat(info) .usingRecursiveComparison() .ignoringFields(ConqueryAuthenticationInfo.Fields.credentials) - .isEqualTo(new ConqueryAuthenticationInfo(USER_3, USER_3_TOKEN_WRAPPED, REALM, true)); - assertThat(STORAGE.getAllUsers()).containsOnly(USER_3); + .isEqualTo(new ConqueryAuthenticationInfo(new UserId(USER_3_NAME), USER_3_TOKEN_WRAPPED, REALM, true)); + assertThat(STORAGE.getAllUsers()).containsOnly(new User(USER_3_NAME, USER_3_LABEL, STORAGE)); assertThat(STORAGE.getAllGroups()).hasSize(1); // Pre-existing group assertThat(STORAGE.getGroup(new GroupId(GROUPNAME_1)).getMembers()).doesNotContain(new UserId(USER_3_NAME)); } diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java index 3c2dd2d9fa..58dc916475 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java +++ b/backend/src/test/java/com/bakdata/conquery/models/auth/LocalAuthRealmTest.java @@ -110,7 +110,7 @@ public void testValidUsernamePassword() { assertThatCode(() -> JWT.decode(jwt)).doesNotThrowAnyException(); assertThat(conqueryTokenRealm.doGetAuthenticationInfo(new BearerToken(jwt)).getPrincipals().getPrimaryPrincipal()) - .isEqualTo(user1); + .isEqualTo(new UserId("TestUser")); } @Test diff --git a/backend/src/test/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealmTest.java b/backend/src/test/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealmTest.java index 434a6bab7f..a2073eb645 100644 --- a/backend/src/test/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealmTest.java +++ b/backend/src/test/java/com/bakdata/conquery/models/auth/oidc/JwtPkceVerifyingRealmTest.java @@ -2,11 +2,8 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; -import com.bakdata.conquery.io.storage.MetaStorage; -import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.auth.JwtPkceVerifyingRealmFactory; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; -import com.bakdata.conquery.util.NonPersistentStoreFactory; import org.apache.commons.lang3.time.DateUtils; import org.apache.shiro.authc.BearerToken; import org.junit.jupiter.api.BeforeAll; @@ -31,8 +28,6 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class JwtPkceVerifyingRealmTest { - - private static final MetaStorage STORAGE = new NonPersistentStoreFactory().createMetaStorage(); private static final String HTTP_REALM_URL = "http://realm.url"; private static final String AUDIENCE = "test_aud"; private static final String ALTERNATIVE_ID_CLAIM = "alternativeId"; @@ -40,7 +35,6 @@ class JwtPkceVerifyingRealmTest { private static RSAPrivateKey PRIVATE_KEY; private static RSAPublicKey PUBLIC_KEY; - @BeforeAll static void setup() throws NoSuchAlgorithmException { // Generate a key pair @@ -56,7 +50,6 @@ static void setup() throws NoSuchAlgorithmException { AUDIENCE, List.of(JwtPkceVerifyingRealmFactory.ScriptedTokenChecker.create("t.getOtherClaims().get(\"groups\").equals(\"conquery\")")), List.of(ALTERNATIVE_ID_CLAIM), - STORAGE, 60); } @@ -65,15 +58,14 @@ static void setup() throws NoSuchAlgorithmException { void verifyToken() { // Setup the expected user id - User expected = new User("Test", "Test", STORAGE); - STORAGE.updateUser(expected); + UserId expected = new UserId("Test"); Date issueDate = new Date(); Date expDate = DateUtils.addMinutes(issueDate, 1); String token = JWT.create() .withIssuer(HTTP_REALM_URL) .withAudience(AUDIENCE) - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withIssuedAt(issueDate) .withExpiresAt(expDate) .withClaim("groups", "conquery") @@ -82,21 +74,21 @@ void verifyToken() { .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThat(REALM.doGetAuthenticationInfo(accessToken).getPrincipals().getPrimaryPrincipal()).isEqualTo(expected); + assertThat(REALM.doGetConqueryAuthenticationInfo(accessToken).getPrincipals().getPrimaryPrincipal()).isEqualTo(expected); } @Test void verifyTokenInLeeway() { // Setup the expected user id - User expected = new User("Test", "Test", STORAGE); + UserId expected = new UserId("Test"); Date issueDate = new Date(); Date expDate = DateUtils.addMinutes(issueDate, -1); String token = JWT.create() .withIssuer(HTTP_REALM_URL) .withAudience(AUDIENCE) - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withIssuedAt(issueDate) .withExpiresAt(expDate) .withClaim("groups", "conquery") @@ -105,7 +97,7 @@ void verifyTokenInLeeway() { .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThat(REALM.doGetAuthenticationInfo(accessToken).getPrincipals().getPrimaryPrincipal()).isEqualTo(expected); + assertThat(REALM.doGetConqueryAuthenticationInfo(accessToken).getPrincipals().getPrimaryPrincipal()).isEqualTo(expected); } @@ -113,8 +105,7 @@ void verifyTokenInLeeway() { void verifyTokenAlternativeId() { // Setup the expected user id - User expected = new User("Test", "Test", STORAGE); - STORAGE.updateUser(expected); + UserId expected = new UserId("Test"); Date issueDate = new Date(); Date expDate = DateUtils.addMinutes(issueDate, 1); @@ -126,11 +117,11 @@ void verifyTokenAlternativeId() { .withClaim("groups", "conquery") .withIssuedAt(issueDate) .withExpiresAt(expDate) - .withClaim(ALTERNATIVE_ID_CLAIM, expected.getName()) + .withClaim(ALTERNATIVE_ID_CLAIM, expected.getEmail()) .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThat(REALM.doGetAuthenticationInfo(accessToken).getPrincipals().getPrimaryPrincipal()).isEqualTo(expected); + assertThat(REALM.doGetConqueryAuthenticationInfo(accessToken).getPrincipals()).containsAll(List.of(new UserId(primId),expected)); } @@ -145,13 +136,13 @@ void falsifyTokenMissingCustomClaim() { String token = JWT.create() .withIssuer(HTTP_REALM_URL) .withAudience(AUDIENCE) - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withIssuedAt(issueDate) .withExpiresAt(expDate) .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThatCode(() -> REALM.doGetAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); + assertThatCode(() -> REALM.doGetConqueryAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); } @Test @@ -165,14 +156,14 @@ void falsifyTokenWrongAudience() { String token = JWT.create() .withIssuer(HTTP_REALM_URL) .withAudience("wrong_aud") - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withClaim("groups", "conquery") .withIssuedAt(issueDate) .withExpiresAt(expDate) .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThatCode(() -> REALM.doGetAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); + assertThatCode(() -> REALM.doGetConqueryAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); } @Test @@ -184,14 +175,14 @@ void falsifyTokenOutdated() { Date expDate = DateUtils.addMinutes(issueDate, -2); String token = JWT.create() .withIssuer(HTTP_REALM_URL) - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withClaim("groups", "conquery") .withIssuedAt(issueDate) .withExpiresAt(expDate) .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThatCode(() -> REALM.doGetAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); + assertThatCode(() -> REALM.doGetConqueryAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); } @Test @@ -205,7 +196,7 @@ void falsifyTokenWrongIssuer() { String token = JWT.create() .withIssuer("wrong_iss") .withAudience(AUDIENCE) - .withSubject(expected.getName()) + .withSubject(expected.getEmail()) .withIssuedAt(issueDate) .withExpiresAt(expDate) .withClaim("groups", "conquery") @@ -214,6 +205,6 @@ void falsifyTokenWrongIssuer() { .sign(Algorithm.RSA256(PUBLIC_KEY, PRIVATE_KEY)); BearerToken accessToken = new BearerToken(token); - assertThatCode(() -> REALM.doGetAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); + assertThatCode(() -> REALM.doGetConqueryAuthenticationInfo(accessToken)).hasCauseInstanceOf(VerificationException.class); } } \ No newline at end of file diff --git a/backend/src/test/java/com/bakdata/conquery/util/NonPersistentStore.java b/backend/src/test/java/com/bakdata/conquery/util/NonPersistentStore.java index 414d81f636..c3bc95d1c5 100644 --- a/backend/src/test/java/com/bakdata/conquery/util/NonPersistentStore.java +++ b/backend/src/test/java/com/bakdata/conquery/util/NonPersistentStore.java @@ -72,7 +72,7 @@ public void clear() { } @Override - public void deleteStore() { + public void removeStore() { clear(); } diff --git a/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java b/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java index 4aeaef5288..a4b6769581 100644 --- a/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java +++ b/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java @@ -1,5 +1,21 @@ package com.bakdata.conquery.util.support; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.net.ServerSocket; +import java.time.Duration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.validation.Validator; +import javax.ws.rs.client.Client; + import com.bakdata.conquery.Conquery; import com.bakdata.conquery.commands.ShardNode; import com.bakdata.conquery.commands.StandaloneCommand; @@ -31,21 +47,6 @@ import org.glassfish.jersey.client.ClientProperties; import org.junit.jupiter.api.extension.ExtensionContext; -import javax.validation.Validator; -import javax.ws.rs.client.Client; -import java.io.File; -import java.net.ServerSocket; -import java.time.Duration; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - /** * Represents the test instance of Conquery. */ diff --git a/backend/src/test/resources/tests/endpoints/apiEndpointInfo.json b/backend/src/test/resources/tests/endpoints/apiEndpointInfo.json index 9010dd8715..a55bb6bdf6 100644 --- a/backend/src/test/resources/tests/endpoints/apiEndpointInfo.json +++ b/backend/src/test/resources/tests/endpoints/apiEndpointInfo.json @@ -72,7 +72,7 @@ { "method": "GET", "path": "/datasets", - "clazz": "DatasetsResource" + "clazz": "APIResource" }, { "method": "GET", diff --git a/docs/Config JSON.md b/docs/Config JSON.md index dec9f103ac..5b3c8d4091 100644 --- a/docs/Config JSON.md +++ b/docs/Config JSON.md @@ -15,7 +15,7 @@ An `AuthorizationConfig` defines the initial users that are created on applicati Different types of AuthorizationConfig can be used by setting `type` to one of the following values: -### DEFAULT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java#L11) +### DEFAULT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/DefaultAuthorizationConfig.java#L12)
Details

@@ -26,7 +26,7 @@ No fields can be set for this type.

-### DEVELOPMENT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java#L16) +### DEVELOPMENT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/DevelopmentAuthorizationConfig.java#L17)
Details

@@ -47,18 +47,7 @@ An `AuthenticationConfig` is used to define how specific realms for authenticati Different types of AuthenticationRealmFactory can be used by setting `type` to one of the following values: -### API_TOKEN [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/auth/ApiTokenRealmFactory.java#L27) - - -

Details

- -Java Type: `com.bakdata.conquery.models.config.auth.ApiTokenRealmFactory` - -No fields can be set for this type. - -

- -### DEVELOPMENT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java#L10-L13) +### DEVELOPMENT [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/auth/develop/DevAuthConfig.java#L9-L12) Default configuration for the auth system. Sets up all other default components. This configuration causes that every request is handled as invoked by the super user.
Details

diff --git a/docs/REST API JSONs.md b/docs/REST API JSONs.md index bfbbbf2838..17ecb6ed97 100644 --- a/docs/REST API JSONs.md +++ b/docs/REST API JSONs.md @@ -7,12 +7,12 @@ Instead of a list ConQuery also always accepts a single element. # REST endpoints -### GET /datasets [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/resources/api/DatasetsResource.java#L26) +### GET /datasets [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/resources/api/APIResource.java#L26)

Details

-Java Type: `com.bakdata.conquery.resources.api.DatasetsResource` +Java Type: `com.bakdata.conquery.resources.api.APIResource` Method: `getDatasets` @@ -857,7 +857,7 @@ Supported Fields: | [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/resources/api/FilterResource.java#L60) | values | list of `String` | `null` | | |

-### Type FormConfig [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L50) +### Type FormConfig [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L49)
Details

@@ -868,18 +868,18 @@ Supported Fields: | | Field | Type | Default | Example | Description | | --- | --- | --- | --- | --- | --- | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L78) | creationTime | `LocalDateTime` | generated default varies | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L59) | dataset | ID of `@NsIdRef Dataset` | `null` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L63) | formId | `@NonNull UUID` | generated default varies | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L61) | formType | `String` | `null` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L65) | label | `String` | `null` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L76) | owner | ID of `@MetaIdRef User` | `null` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L68) | shared | `boolean` | `false` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L66) | tags | list of `String` | `[]` | | | -| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L70-L73) | values | `@NotNull JsonNode` | `null` | | This is a blackbox for us at the moment, where the front end saves the state of the formular, when the user saved it. | -

- -### Type FormConfigFullRepresentation [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L175-L178) +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L77) | creationTime | `LocalDateTime` | generated default varies | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L58) | dataset | ID of `@NsIdRef Dataset` | `null` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L62) | formId | `@NonNull UUID` | generated default varies | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L60) | formType | `String` | `null` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L64) | label | `String` | `null` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L75) | owner | ID of `@MetaIdRef User` | `null` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L67) | shared | `boolean` | `false` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L65) | tags | list of `String` | `[]` | | | +| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L69-L72) | values | `@NotNull JsonNode` | `null` | | This is a blackbox for us at the moment, where the front end saves the state of the formular, when the user saved it. | +

+ +### Type FormConfigFullRepresentation [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L174-L177) API representation for a single {@link FormConfig} which includes the form fields an their values.
Details

@@ -890,7 +890,7 @@ No fields can be set for this type.

-### Type FormConfigOverviewRepresentation [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L151-L154) +### Type FormConfigOverviewRepresentation [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/forms/configs/FormConfig.java#L150-L153) API representation for the overview of all {@link FormConfig}s which does not include the form fields an their values.
Details