CB-6271 add timeout of inaction in configuration mode (#3289)

* CB-6271 add timeout of inaction in configuration mode

* CB-6271 add timeout of inaction in configuration mode

* CB-6271 add timeout of inaction in configuration mode

* CB-6271 add timeout of inaction in configuration mode

* CB-6271 fix insecure default configuration set timeout to 2 mins

* CB-6271 fix insecure default configuration set timeout to one min

* CB-6271 handle configuration error properly

* CB-6271 catch unhandled error on validation step

* CB-6271 add return type

* CB-6271 fix insecure default configuration set timeout to one min

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
This commit is contained in:
Ruslan Musaev
2025-03-18 06:21:27 +01:00
committed by GitHub
co-authored by Daria Marutkina naumov
parent 6c44f8c432
commit 34f4063114
12 changed files with 126 additions and 20 deletions
@@ -40,6 +40,7 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.model.utils,
io.cloudbeaver.registry,
io.cloudbeaver.server,
io.cloudbeaver.server.filters,
io.cloudbeaver.service,
io.cloudbeaver.service.admin,
io.cloudbeaver.service.security,
@@ -0,0 +1,64 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.filters;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentationContext;
import graphql.execution.instrumentation.SimplePerformantInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
import graphql.validation.ValidationError;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.dbeaver.Log;
import java.time.Duration;
import java.util.List;
public class ServerConfigurationTimeLimitFilter extends SimplePerformantInstrumentation {
private static final Log log = Log.getLog(ServerConfigurationTimeLimitFilter.class);
private static final int MINUTES_OF_INACTION_BEFORE_DISABLING_REQUEST_PROCESSING = 60;
private final ServletApplication application;
public ServerConfigurationTimeLimitFilter(ServletApplication application) {
this.application = application;
}
@Override
public InstrumentationContext<List<ValidationError>> beginValidation(
InstrumentationValidationParameters parameters,
InstrumentationState state
) {
boolean isOutOfTime = System.currentTimeMillis() - application.getApplicationStartTime()
> Duration.ofMinutes(MINUTES_OF_INACTION_BEFORE_DISABLING_REQUEST_PROCESSING).toMillis();
if (application.isConfigurationMode() && isOutOfTime) {
log.warn("Server configuration time has expired. A server restart is required to continue.");
ValidationError error = ValidationError.newValidationError()
.description("Server configuration time has expired. A server restart is required to continue.")
.build();
return new SimpleInstrumentationContext<>() {
@Override
public void onCompleted(List<ValidationError> result, Throwable t) {
result.clear();
result.add(error);
}
};
}
return super.beginValidation(parameters, state);
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.filters.ServerConfigurationTimeLimitFilter;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.WebStatusServlet;
@@ -115,7 +116,13 @@ public class CBJettyServer {
servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status");
servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), serverConfiguration.getServicesURI() + "gql/*");
servletContextHandler.addServlet(
new ServletHolder(
"graphql",
new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application))
),
serverConfiguration.getServicesURI() + "gql/*"
);
servletContextHandler.addEventListener(new CBServerContextListener(application));
// Add extensions from services
@@ -19,7 +19,7 @@ package io.cloudbeaver.server.graphql;
import com.google.gson.*;
import graphql.*;
import graphql.execution.*;
import graphql.execution.instrumentation.SimplePerformantInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import graphql.language.SourceLocation;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLSchema;
@@ -51,7 +51,10 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class GraphQLEndpoint extends HttpServlet {
@@ -73,13 +76,13 @@ public class GraphQLEndpoint extends HttpServlet {
.create();
private GraphQLBindingContext bindingContext;
public GraphQLEndpoint() {
public GraphQLEndpoint(Instrumentation instrumentation) {
GraphQLSchema schema = buildSchema();
PropertyDataFetcherHelper.setUseLambdaFactory(false);
graphQL = GraphQL
.newGraphQL(schema)
.instrumentation(new SimplePerformantInstrumentation())
.instrumentation(instrumentation)
.queryExecutionStrategy(new WebExecutionStrategy())
.mutationExecutionStrategy(new WebExecutionStrategy())
.build();
@@ -157,17 +157,21 @@ export class ConfigurationWizardService {
}
private async finish() {
for (const step of this.steps) {
if (step?.configurationWizardOptions?.onConfigurationFinish) {
await step.configurationWizardOptions.onConfigurationFinish();
try {
for (const step of this.steps) {
if (step?.configurationWizardOptions?.onConfigurationFinish) {
await step.configurationWizardOptions.onConfigurationFinish();
}
}
}
this.administrationScreenService.clearItemsState();
this.administrationScreenService.navigateToRoot();
this.notificationService.logSuccess({
title: 'administration_configuration_wizard_finish_success_title',
message: 'administration_configuration_wizard_finish_success_message',
});
this.administrationScreenService.clearItemsState();
this.administrationScreenService.navigateToRoot();
this.notificationService.logSuccess({
title: 'administration_configuration_wizard_finish_success_title',
message: 'administration_configuration_wizard_finish_success_message',
});
} catch (exception: any) {
this.notificationService.logException(exception, 'core_administration_configuration_wizard_finish_fail_title');
}
}
}
@@ -4,4 +4,5 @@ export default [
['administration_configuration_wizard_title', 'Initial Server Configuration'],
['administration_configuration_wizard_finish_success_title', 'Server configured'],
['administration_configuration_wizard_finish_success_message', 'You can log-in as administrator in order to set up additional parameters.'],
['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'],
];
@@ -14,4 +14,5 @@ export default [
'administration_configuration_wizard_finish_success_message',
"Vous pouvez vous connecter en tant qu'administrateur pour configurer des paramètres supplémentaires.",
],
['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'],
];
@@ -4,4 +4,5 @@ export default [
['administration_configuration_wizard_title', 'Configurazione Iniziale del Server'],
['administration_configuration_wizard_finish_success_title', 'Server configured'],
['administration_configuration_wizard_finish_success_message', 'You can log-in as administrator in order to set up additional parameters.'],
['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'],
];
@@ -4,4 +4,5 @@ export default [
['administration_configuration_wizard_title', 'Начальная настройка сервера'],
['administration_configuration_wizard_finish_success_title', 'Сервер настроен'],
['administration_configuration_wizard_finish_success_message', 'Вы можете войти администратором чтобы настроить дополнительные параметры.'],
['core_administration_configuration_wizard_finish_fail_title', 'Не удалось настроить сервер'],
];
@@ -11,4 +11,5 @@ export default [
['administration_configuration_wizard_title', '初始化服务器配置'],
['administration_configuration_wizard_finish_success_title', '服务器已完成配置'],
['administration_configuration_wizard_finish_success_message', '您可以使用管理员登录后进行更多参数配置。'],
['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'],
];
+11 -2
View File
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
this.formState.submitTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.save.bind(this)));
this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.format.bind(this)));
this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.validate.bind(this)));
this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.handleValidation.bind(this)));
makeObservable<this, 'loaded' | 'loading' | 'setInitialState'>(this, {
initialState: observable,
@@ -146,6 +146,15 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
this.setState(toJS(this.initialState));
}
private async handleValidation(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): Promise<void> {
try {
this.exception = null;
await this.validate(data, contexts);
} catch (exception: any) {
this.exception = exception;
}
}
protected setInitialState(initialState: TPartState) {
this.initialState = initialState;
@@ -11,6 +11,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { formValidationContext } from '@cloudbeaver/core-ui';
import { getFirstException } from '@cloudbeaver/core-utils';
import { ADMINISTRATION_SERVER_CONFIGURATION_ITEM } from './ServerConfiguration/ADMINISTRATION_SERVER_CONFIGURATION_ITEM.js';
import { ServerConfigurationFormStateManager } from './ServerConfiguration/ServerConfigurationFormStateManager.js';
@@ -71,8 +72,20 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
return true;
},
onConfigurationFinish: async () => {
await this.serverConfigurationFormStateManager.formState?.save();
await this.sessionDataResource.refresh();
const state = this.serverConfigurationFormStateManager.formState;
if (state) {
const saved = await state.save();
if (!saved) {
const error = getFirstException(state.exception);
if (error) {
throw getFirstException(error);
}
}
await this.sessionDataResource.refresh();
}
},
onLoad: () => {
this.serverConfigurationFormStateManager.create();