valepakh commented on code in PR #2472: URL: https://github.com/apache/ignite-3/pull/2472#discussion_r1307142086
########## modules/cli/src/main/java/org/apache/ignite/internal/cli/call/connect/ConnectWizardCall.java: ########## @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.cli.call.connect; + +import static org.apache.ignite.internal.cli.config.CliConfigKeys.BASIC_AUTHENTICATION_PASSWORD; +import static org.apache.ignite.internal.cli.config.CliConfigKeys.BASIC_AUTHENTICATION_USERNAME; + +import io.micronaut.http.HttpStatus; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import javax.net.ssl.SSLException; +import org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion; +import org.apache.ignite.internal.cli.config.CliConfigKeys; +import org.apache.ignite.internal.cli.config.ConfigManager; +import org.apache.ignite.internal.cli.config.ConfigManagerProvider; +import org.apache.ignite.internal.cli.core.call.Call; +import org.apache.ignite.internal.cli.core.call.CallOutput; +import org.apache.ignite.internal.cli.core.call.DefaultCallOutput; +import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException; +import org.apache.ignite.internal.cli.core.flow.Flowable; +import org.apache.ignite.internal.cli.core.flow.builder.FlowBuilder; +import org.apache.ignite.internal.cli.core.rest.ApiClientFactory; +import org.apache.ignite.internal.cli.core.rest.ApiClientSettings; +import org.apache.ignite.lang.util.StringUtils; +import org.apache.ignite.rest.client.api.NodeConfigurationApi; +import org.apache.ignite.rest.client.invoker.ApiClient; +import org.apache.ignite.rest.client.invoker.ApiException; + +/** + * Call which tries to connect to the Ignite 3 node and in case of error (SSL error or auth error) asks the user for the + * SSL configuration or Auth configuration and tries again. + */ +@Singleton +public class ConnectWizardCall implements Call<ConnectCallInput, String> { + @Inject + private ConnectCall connectCall; + + @Inject + private ConfigManagerProvider configManagerProvider; + + @Inject + private ApiClientFactory clientFactory; + + @Override + public CallOutput<String> execute(ConnectCallInput input) { + CallOutput<String> output = connectCall.execute(input); + if (output.hasError()) { + return processErrorOutput(input, output); + } + return output; + } + + private CallOutput<String> processErrorOutput(ConnectCallInput input, CallOutput<String> output) { + if (output.errorCause().getCause() instanceof ApiException) { + ApiException cause = (ApiException) output.errorCause().getCause(); + Throwable apiCause = cause.getCause(); + + if (apiCause instanceof SSLException) { // Configure SSL + return configureSsl(input, output); + } else if (cause.getCode() == HttpStatus.UNAUTHORIZED.getCode()) { // Configure rest basic authentication + return configureAuth(input, output); + } + } + return output; + } + + private CallOutput<String> configureSsl(ConnectCallInput input, CallOutput<String> output) { + + FlowBuilder<Void, SslConfig> flowBuilder = ConnectToClusterQuestion.askQuestionOnSslError(); + Flowable<SslConfig> result = flowBuilder.build().start(Flowable.empty()); + if (result.hasResult()) { + try { + checkConnectionSsl(new ConnectSslConfigCallInput(input.url(), result.value())); + saveConfigSsl(result.value()); + return connectCall.execute(input); + } catch (ApiException exception) { + Throwable apiCause = exception.getCause(); + + // SSL params were wrong + if (apiCause instanceof SSLException) { + return DefaultCallOutput.failure(new IgniteCliApiException(exception, input.url())); + } else { + // SSL params are correct but auth params not provided + saveConfigSsl(result.value()); + return configureAuth(input, output); + } + } catch (IgniteCliApiException cliApiException) { + return DefaultCallOutput.failure(cliApiException); + } + } + return output; + } + + private CallOutput<String> configureAuth(ConnectCallInput input, CallOutput<String> output) { + FlowBuilder<Void, AuthConfig> flowBuilder = ConnectToClusterQuestion.askQuestionOnAuthError(); + Flowable<AuthConfig> result = flowBuilder.build().start(Flowable.empty()); + if (result.hasResult()) { + ConnectCallInput connectCallInput = ConnectCallInput.builder() + .url(input.url()) + .username(result.value().username()) + .password(result.value().password()) + .build(); + try { + checkConnection(connectCallInput); + saveCredentials(connectCallInput); + return connectCall.execute(connectCallInput); Review Comment: I don't get it. `ConnectCall` will still try to connect without authentication. You added the `if` in it, then removed it. ########## modules/cli/src/main/java/org/apache/ignite/internal/cli/call/connect/ConnectSslCall.java: ########## @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.ignite.internal.cli.call.connect; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import javax.net.ssl.SSLException; -import org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion; -import org.apache.ignite.internal.cli.core.call.Call; -import org.apache.ignite.internal.cli.core.call.CallOutput; -import org.apache.ignite.internal.cli.core.flow.Flowable; -import org.apache.ignite.internal.cli.core.flow.builder.FlowBuilder; -import org.apache.ignite.rest.client.invoker.ApiException; - -/** - * Call which tries to connect to the Ignite 3 node and in case of SSL error asks the user for the SSL configuration and tries again. - */ -@Singleton -public class ConnectSslCall implements Call<ConnectCallInput, String> { - @Inject - private ConnectCall connectCall; - - @Inject - private ConnectSslConfigCall connectSslConfigCall; Review Comment: `ConnectSslConfigCall` with corresponding input is not used anymore. ########## modules/cli/src/main/java/org/apache/ignite/internal/cli/call/connect/ConnectAuthConfigCall.java: ########## @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.cli.call.connect; + +import static org.apache.ignite.internal.cli.config.CliConfigKeys.BASIC_AUTHENTICATION_PASSWORD; +import static org.apache.ignite.internal.cli.config.CliConfigKeys.BASIC_AUTHENTICATION_USERNAME; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.apache.ignite.internal.cli.config.ConfigManager; +import org.apache.ignite.internal.cli.config.ConfigManagerProvider; +import org.apache.ignite.internal.cli.core.call.Call; +import org.apache.ignite.internal.cli.core.call.CallOutput; +import org.apache.ignite.internal.cli.core.call.DefaultCallOutput; +import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException; +import org.apache.ignite.internal.cli.core.rest.ApiClientFactory; +import org.apache.ignite.internal.cli.core.rest.ApiClientSettings; +import org.apache.ignite.rest.client.api.NodeConfigurationApi; +import org.apache.ignite.rest.client.invoker.ApiClient; +import org.apache.ignite.rest.client.invoker.ApiException; + +/** + * Call which connects to the Ignite 3 node with provided basic auth configuration and saves the configuration to the cli config. + */ +@Singleton +public class ConnectAuthConfigCall implements Call<ConnectCallInput, String> { Review Comment: This class is not used anymore. ########## modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/IgniteCliApiExceptionHandler.java: ########## @@ -83,6 +84,16 @@ public int handle(ExceptionWriter err, IgniteCliApiException e) { errorComponentBuilder.header(header(e)); } } + } else if (e.getCause() instanceof IOException) { + errorComponentBuilder + .header("SSL error") + .details(e.getCause().getMessage()) + .verbose(e.getMessage()); + } else if (e.getCause() instanceof IllegalArgumentException) { + errorComponentBuilder + .header("SSL error") + .details(e.getCause().getMessage()) + .verbose(e.getMessage()); Review Comment: This looks like a very broad claim, that any `IOException` or `IAE` necessarily mean SSL error. ########## modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/questions/ItConnectToSslAndAuthClusterTest.java: ########## @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.internal.cli.commands.questions; + +import static org.apache.ignite.internal.cli.commands.cliconfig.TestConfigManagerHelper.readClusterConfigurationWithEnabledAuth; +import static org.apache.ignite.internal.testframework.IgniteTestUtils.escapeWindowsPath; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import org.apache.ignite.InitParametersBuilder; +import org.apache.ignite.internal.NodeConfig; +import org.apache.ignite.internal.cli.commands.ItConnectToClusterTestBase; +import org.apache.ignite.internal.cli.commands.cliconfig.TestConfigManagerHelper; +import org.apache.ignite.internal.cli.config.CliConfigKeys; +import org.apache.ignite.internal.cli.config.TestStateConfigHelper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ItConnectToSslAndAuthClusterTest extends ItConnectToClusterTestBase { + @Override + protected String nodeBootstrapConfigTemplate() { + return NodeConfig.REST_SSL_BOOTSTRAP_CONFIG; + } + + @Override + protected void configureInitParameters(InitParametersBuilder builder) { + builder.clusterConfiguration(readClusterConfigurationWithEnabledAuth()); + } + + @Test + @DisplayName("Should ask for SSL configuration connect to last connected cluster HTTPS url than ask for auth") + void connectOnStartAskSslAfterAskAuth() throws IOException { + // Given prompt before connect + assertThat(getPrompt()).isEqualTo("[disconnected]> "); + + // And default URL is HTTPS + configManagerProvider.setConfigFile(TestConfigManagerHelper.createClusterUrlSslConfig()); + + // And trust store is not configured + + // And last connected URL is equal to the default URL + stateConfigProvider.config = TestStateConfigHelper.createLastConnectedSslDefault(); + + // And answer to the reconnect question is "y", to the SSL configuration question is "y", + // trust store path and password are provided and key store is not configured + bindAnswers("y", "y", NodeConfig.resolvedTruststorePath, NodeConfig.trustStorePassword, "n", "y", "admin", "password"); + + // When asked the question + question.askQuestionOnReplStart(); + + // Then + assertAll( + this::assertErrOutputIsEmpty, + () -> assertOutputContains("Connected to https://localhost:10400") + ); + // And prompt is changed to connect + String promptAfter = getPrompt(); + assertThat(promptAfter).isEqualTo("[admin:" + nodeName() + "]> "); Review Comment: ```suggestion assertThat(getPrompt()).isEqualTo("[admin:" + nodeName() + "]> "); ``` ########## modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/connect/ItConnectWithBasicAuthenticationCommandTest.java: ########## @@ -45,23 +45,23 @@ protected void configureInitParameters(InitParametersBuilder builder) { } @Test - void failToConnectWithoutAuthentication() { + void failToConnectWithoutAuthentication() throws IOException { // Given basic authentication is not configured in config file // And prompt before connect assertThat(getPrompt()).isEqualTo("[disconnected]> "); + bindAnswers("n"); Review Comment: Could you please clarify for what question this is the answer and why there's no error after executing command? I assume that's because the question prompt is not piped through the output streams. I wonder whether we could improve testing here and test what question was asked, not in this ticket though. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
