This is an automated email from the ASF dual-hosted git repository. apkhmv pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new 2b341b4fec IGNITE-20643 Migrate basic auth provider to users list (#2921) 2b341b4fec is described below commit 2b341b4fecd88886e6001401261b77e64bceb8e0 Author: Mikhail <pochat...@users.noreply.github.com> AuthorDate: Thu Dec 7 21:35:58 2023 +0300 IGNITE-20643 Migrate basic auth provider to users list (#2921) --- .../configuration/ItConfigCommandTest.java | 14 +- .../cluster-configuration-with-enabled-auth.conf | 27 +-- .../ignite/client/handler/ItClientHandlerTest.java | 3 +- .../handler/ClientInboundMessageHandlerTest.java | 107 ++++----- .../ignite/client/ClientAuthenticationTest.java | 14 +- modules/distribution-zones/build.gradle | 1 + ...niteDistributionZoneManagerNodeRestartTest.java | 8 +- .../ignite/jdbc/ItJdbcAuthenticationTest.java | 10 +- .../rest/ItInitializedClusterRestTest.java | 2 +- .../rest/authentication/ItAuthenticationTest.java | 265 +++++++-------------- .../runner/app/PlatformTestNodeRunner.java | 5 +- .../AuthenticationProviderEqualityVerifier.java | 22 +- .../authentication/AuthenticatorFactory.java | 8 +- .../SecurityConfigurationModule.java | 15 +- ...cAuthenticationProviderConfigurationSchema.java | 16 +- .../authentication/basic/BasicAuthenticator.java | 37 +-- ...iderConfigurationSchema.java => BasicUser.java} | 35 +-- ...hema.java => BasicUserConfigurationSchema.java} | 15 +- .../AuthenticationProvidersValidatorImpl.java | 39 ++- .../AuthenticationManagerImplTest.java | 10 +- .../AuthenticationProvidersValidatorImplTest.java | 5 +- .../SecurityConfigurationModuleTest.java | 13 +- ...henticationProviderConfigurationSchemaTest.java | 10 +- .../basic/BasicAuthenticatorTest.java | 6 +- 24 files changed, 329 insertions(+), 358 deletions(-) diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java index e7263f0df7..ff9453151a 100644 --- a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java +++ b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java @@ -119,7 +119,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { @DisplayName("Should update config with key-value format when valid cluster-endpoint-url is given") void updateClusterConfigWithoutQuoting() { execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "security.authentication.providers.basic1={type=basic,username=asd,password=asadf}"); + "security.authentication.providers.default={type=basic,users=[{username=asd,password=pass1}]}"); assertAll( this::assertExitCodeIsZero, @@ -130,7 +130,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { //Emulate config with spaces execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "security.authentication.providers.basic2", "=", "{", "type=basic,", "username=asd,", "password=asadf}"); + "security.authentication.providers.default", "=", "{", "type=basic,", "users=[{", "username=asd,", "password=pass2}]}"); assertAll( this::assertExitCodeIsZero, @@ -144,7 +144,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { void updateClusterWithQuotedArgs() { //Emulate quoting config execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "\"security.authentication.providers.basic3={type=basic,username=asd,password=asadf}\""); + "\"security.authentication.providers.default={type=basic,users=[{username=asd,password=pass3}]}\""); assertAll( this::assertExitCodeIsZero, @@ -154,7 +154,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { //Emulate quoting config execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "\"security.authentication.providers.basic4\"", "\"={type=basic,username=asd,password=asadf}\""); + "\"security.authentication.providers.default\"", "\"={type=basic,users=[{username=asd,password=pass4}]}\""); assertAll( this::assertExitCodeIsZero, @@ -164,7 +164,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { //Emulate quoting config execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "security.authentication.providers.basic5", "\"={type=basic,username=asd,password=asadf}\""); + "security.authentication.providers.default", "\"={type=basic,users=[{username=asd,password=pass5}]}\""); assertAll( this::assertExitCodeIsZero, @@ -177,7 +177,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { @DisplayName("Test using arguments in parameters") void useOptionsInArguments() { execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "security.authentication.providers.basic6={type=basic,username:", "--verbose,", "password=--verbose}"); + "security.authentication.providers.default={type=basic,users=[{username:", "--verbose,", "password=--verbose}]}"); assertAll( () -> assertExitCodeIs(2), @@ -188,7 +188,7 @@ class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase { resetOutput(); execute("cluster", "config", "update", "--cluster-endpoint-url", NODE_URL, - "\"security.authentication.providers.basic7={type=basic,username: --verbose, password=--verbose}\""); + "\"security.authentication.providers.default={type=basic,users=[{username: --verbose, password=--verbose}]}\""); assertAll( this::assertExitCodeIsZero, diff --git a/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf b/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf index f9461400ef..211f31d24d 100644 --- a/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf +++ b/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf @@ -1,19 +1,18 @@ security: { enabled: true, authentication: { - providers: [ - { - name: basic, - type: basic, - username: admin, - password: password - }, - { - name: basic1, - type: basic, - username: admin1, - password: password - } - ] + providers.default: { + type: basic, + users: [ + { + username: admin, + password: password + }, + { + username: admin1, + password: password + } + ] + } } } diff --git a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java index 4d4adaae6f..288552f77b 100644 --- a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java +++ b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java @@ -460,8 +460,7 @@ public class ItClientHandlerTest extends BaseIgniteAbstractTest { change.changeEnabled(true); change.changeAuthentication().changeProviders().create("basic", authenticationProviderChange -> { authenticationProviderChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername(username) - .changePassword(password); + .changeUsers(users -> users.create(username, user -> user.changePassword(password))); }); }).join(); } diff --git a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java index 595d4b4656..db97e65c78 100644 --- a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java +++ b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java @@ -17,7 +17,9 @@ package org.apache.ignite.client.handler; +import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -69,6 +71,8 @@ import org.msgpack.core.MessagePack; class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest { private static final Duration TIMEOUT_OF_DURING = Duration.ofSeconds(2); + private static final String PROVIDER_NAME = "basic"; + @InjectConfiguration private ClientConnectorConfiguration configuration; @@ -166,22 +170,20 @@ class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest { authenticationManager.listen(handler); securityConfiguration.listen(authenticationManager); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(true); change.changeAuthentication(authChange -> { authChange.changeProviders(providersChange -> { - providersChange.create("basic", basicChange -> { - basicChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin") - .changePassword("password"); - }).create("basic1", basicChange -> { + providersChange.create(PROVIDER_NAME, basicChange -> { basicChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin1") - .changePassword("password"); + .changeUsers(users -> + users.create("admin", user -> user.changePassword("password")) + .create("admin1", user -> user.changePassword("password"))); }); }); }); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); handler.channelRegistered(ctx); } @@ -190,80 +192,49 @@ class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest { void disableAuthentication() throws IOException { handshake(); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(false); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false)); } @Test - void enableAuthentication() throws InterruptedException, IOException { - securityConfiguration.change(change -> { + void enableAuthentication() throws IOException { + CompletableFuture<Void> future1 = securityConfiguration.change(change -> { change.changeEnabled(false); - }).join(); - - handshake(); - - securityConfiguration.change(change -> { - change.changeEnabled(true); - }).join(); - - await().untilAtomic(ctxClosed, is(true)); - } + }); + assertThat(future1, willCompleteSuccessfully()); - @Test - void changeCurrentProvider() throws IOException { handshake(); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(true); - change.changeAuthentication(authChange -> { - authChange.changeProviders(providersChange -> { - providersChange.update("basic", basicChange -> { - basicChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin") - .changePassword("new-password"); - }); - }); - }); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); await().untilAtomic(ctxClosed, is(true)); } @Test - void changeAnotherProvider() throws IOException { + void changeProvider() throws IOException { handshake(); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(true); change.changeAuthentication(authChange -> { authChange.changeProviders(providersChange -> { - providersChange.update("basic1", basicChange -> { + providersChange.update(PROVIDER_NAME, basicChange -> { basicChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin1") - .changePassword("new-password"); + .changeUsers(users -> + users.update("admin", user -> user.changePassword("new-password")) + ); }); }); }); - }).join(); - - await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false)); - } - - @Test - void deleteCurrentProvider() throws IOException { - handshake(); - - securityConfiguration.change(change -> { - change.changeEnabled(true); - change.changeAuthentication(authChange -> { - authChange.changeProviders(providersChange -> { - providersChange.delete("basic"); - }); - }); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); await().untilAtomic(ctxClosed, is(true)); } @@ -272,34 +243,38 @@ class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest { void deleteAnotherProvider() throws IOException { handshake(); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(true); change.changeAuthentication(authChange -> { authChange.changeProviders(providersChange -> { providersChange.delete("basic1"); }); }); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false)); } @Test void createNewProvider() throws IOException { + String newProviderName = "basic2"; handshake(); - securityConfiguration.change(change -> { + CompletableFuture<Void> future = securityConfiguration.change(change -> { change.changeEnabled(true); change.changeAuthentication(authChange -> { authChange.changeProviders(providersChange -> { - providersChange.create("basic2", basicChange -> { + providersChange.create(newProviderName, basicChange -> { basicChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin2") - .changePassword("admin"); + .changeUsers(users -> + users.create("admin2", user -> user.changePassword("admin")) + ); }); }); }); - }).join(); + }); + assertThat(future, willCompleteSuccessfully()); await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false)); } @@ -315,7 +290,7 @@ class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest { packer.packBinaryHeader(0); // Features. packer.packInt(3); // Extensions. packer.packString("authn-type"); - packer.packString("basic"); + packer.packString(PROVIDER_NAME); packer.packString("authn-identity"); packer.packString("admin"); packer.packString("authn-secret"); diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java index 7c109a5667..5741879c3f 100644 --- a/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java +++ b/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java @@ -115,12 +115,14 @@ public class ClientAuthenticationTest extends BaseIgniteAbstractTest { null); if (basicAuthn) { - securityConfiguration.change(change -> { - change.changeEnabled(true); - change.changeAuthentication().changeProviders().create("basic", authenticationProviderChange -> - authenticationProviderChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("usr") - .changePassword("pwd")); + securityConfiguration.change(securityChange -> { + securityChange.changeEnabled(true); + securityChange.changeAuthentication().changeProviders().create("basic", change -> + change.convert(BasicAuthenticationProviderChange.class) + .changeUsers(users -> users.create("usr", user -> + user.changePassword("pwd")) + ) + ); }).join(); } diff --git a/modules/distribution-zones/build.gradle b/modules/distribution-zones/build.gradle index 33fb0e3ccc..7e948adc28 100644 --- a/modules/distribution-zones/build.gradle +++ b/modules/distribution-zones/build.gradle @@ -88,6 +88,7 @@ dependencies { integrationTestImplementation project(':ignite-affinity') integrationTestImplementation project(':ignite-network-api') integrationTestImplementation project(':ignite-network') + integrationTestImplementation project(':ignite-security') integrationTestImplementation project(':ignite-system-view-api') integrationTestImplementation testFixtures(project(':ignite-core')) integrationTestImplementation testFixtures(project(':ignite-runner')) diff --git a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java index b21564d4c0..5095725906 100644 --- a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java +++ b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java @@ -56,6 +56,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -65,6 +66,7 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.Consumer; import java.util.function.LongFunction; import java.util.stream.Stream; +import org.apache.ignite.configuration.validation.Validator; import org.apache.ignite.internal.BaseIgniteRestartTest; import org.apache.ignite.internal.catalog.CatalogManager; import org.apache.ignite.internal.catalog.CatalogManagerImpl; @@ -98,6 +100,7 @@ import org.apache.ignite.internal.metastorage.server.TestRocksDbKeyValueStorage; import org.apache.ignite.internal.metastorage.server.raft.MetaStorageWriteHandler; import org.apache.ignite.internal.network.configuration.NetworkConfiguration; import org.apache.ignite.internal.network.recovery.VaultStateIds; +import org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl; import org.apache.ignite.internal.testframework.TestIgnitionManager; import org.apache.ignite.internal.util.ByteUtils; import org.apache.ignite.internal.vault.VaultManager; @@ -219,11 +222,14 @@ public class ItIgniteDistributionZoneManagerNodeRestartTest extends BaseIgniteRe modules.distributed().polymorphicSchemaExtensions() ); + Set<Validator<?, ?>> validators = new HashSet<>(modules.distributed().validators()); + validators.remove(AuthenticationProvidersValidatorImpl.INSTANCE); + var clusterCfgMgr = new ConfigurationManager( modules.distributed().rootKeys(), cfgStorage, distributedConfigurationGenerator, - ConfigurationValidatorImpl.withDefaultValidators(distributedConfigurationGenerator, modules.distributed().validators()) + ConfigurationValidatorImpl.withDefaultValidators(distributedConfigurationGenerator, validators) ); ConfigurationRegistry clusterConfigRegistry = clusterCfgMgr.configurationRegistry(); diff --git a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java index e264223ecd..8ea08ba7f7 100644 --- a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java +++ b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java @@ -76,10 +76,14 @@ class ItJdbcAuthenticationTest extends IgniteIntegrationTest { + " \"authentication\": {\n" + " \"providers\": [\n" + " {\n" - + " \"name\": \"basic\",\n" + + " \"name\": \"default\",\n" + " \"type\": \"basic\",\n" - + " \"username\": \"usr\",\n" - + " \"password\": \"pwd\"\n" + + " \"users\": [\n" + + " {\n" + + " \"username\": \"usr\",\n" + + " \"password\": \"pwd\"\n" + + " }\n" + + " ]\n" + " }\n" + " ]\n" + " }\n" diff --git a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java index d04eafa616..3ee30dba4c 100644 --- a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java +++ b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java @@ -173,7 +173,7 @@ public class ItInitializedClusterRestTest extends AbstractRestTestBase { 400, containsString( "Validation did not pass for keys: " - + "[security.authentication.providers, Providers must be present, if security is enabled]" + + "[security.authentication.providers, Default provider default is not removable.]" ) ) ); diff --git a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java index b87527c357..7b14d7196b 100644 --- a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java +++ b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java @@ -17,258 +17,179 @@ package org.apache.ignite.internal.rest.authentication; - -import static java.util.stream.Collectors.toList; -import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName; import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition; -import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully; import static org.apache.ignite.internal.testframework.matchers.HttpResponseMatcher.hasStatusCode; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import java.net.InetAddress; import java.net.URI; +import java.net.UnknownHostException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; -import java.nio.file.Path; import java.time.Duration; import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.stream.IntStream; -import org.apache.ignite.internal.rest.RestNode; -import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; -import org.apache.ignite.internal.testframework.WorkDirectory; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.ignite.InitParametersBuilder; +import org.apache.ignite.internal.ClusterPerTestIntegrationTest; +import org.apache.ignite.internal.app.IgniteImpl; import org.apache.ignite.internal.testframework.WorkDirectoryExtension; -import org.junit.jupiter.api.AfterEach; +import org.apache.ignite.network.NetworkAddress; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtendWith; - /** Tests for the REST authentication configuration. */ @ExtendWith(WorkDirectoryExtension.class) -public class ItAuthenticationTest extends BaseIgniteAbstractTest { - +public class ItAuthenticationTest extends ClusterPerTestIntegrationTest { /** HTTP client that is expected to be defined in subclasses. */ private HttpClient client; - private List<RestNode> nodes; + private String username = "admin"; + + private String password = "password"; - @WorkDirectory - private Path workDir; + private boolean enableAuth = true; @BeforeEach - void setUp(TestInfo testInfo) { + @Override + public void setup(TestInfo testInfo) { client = HttpClient.newBuilder() .build(); - - nodes = IntStream.range(0, 3) - .mapToObj(id -> { - return RestNode.builder() - .workDir(workDir) - .name(testNodeName(testInfo, id)) - .networkPort(3344 + id) - .httpPort(10300 + id) - .httpsPort(10400 + id) - .sslEnabled(false) - .dualProtocol(false) - .build(); - }) - .collect(toList()); - - nodes.stream().parallel().forEach(RestNode::start); } - @Test - public void disabledAuthentication(TestInfo testInfo) throws IOException, InterruptedException { - RestNode metaStorageNode = nodes.get(0); + @Override + protected void customizeInitParameters(InitParametersBuilder builder) { + String config = "security.enabled=" + enableAuth; + + if (username != null && password != null) { + config += ",\n" + + "security.authentication.providers.default={" + + "type=basic," + + "users=[{username=" + username + + ",password=" + password + + "}]}"; + } - // When. - String initClusterBody = "{\n" - + " \"metaStorageNodes\": [\n" - + " \"" + metaStorageNode.name() + "\"\n" - + " ],\n" - + " \"cmgNodes\": [],\n" - + " \"clusterName\": \"cluster\"\n" - + "}"; + builder.clusterConfiguration(config); + } - initCluster(metaStorageNode.httpAddress(), initClusterBody); + @Test + public void disabledAuthentication(TestInfo testInfo) throws Exception { + enableAuth = false; + super.setup(testInfo); // Then. - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "", "")); - } + cluster.runningNodes().forEach(node -> + assertTrue(isRestAvailable(node.restHttpAddress(), "", "")) + ); } @Test - public void defaultUser(TestInfo testInfo) throws InterruptedException, IOException { - // When. - RestNode metaStorageNode = nodes.get(0); - - String initClusterBody = "{\n" - + " \"metaStorageNodes\": [\n" - + " \"" + metaStorageNode.name() + "\"\n" - + " ],\n" - + " \"cmgNodes\": [],\n" - + " \"clusterName\": \"cluster\",\n" - + " \"clusterConfiguration\": \"{" - + " security.enabled:true" - + " }\"\n" - + " }"; - - initCluster(metaStorageNode.httpAddress(), initClusterBody); + public void defaultUser(TestInfo testInfo) throws Exception { + username = null; + password = null; + super.setup(testInfo); - // Then. // Authentication is enabled. - for (RestNode node : nodes) { - assertTrue(waitForCondition(() -> isRestNotAvailable(node.httpAddress(), "", ""), + Set<IgniteImpl> nodes = cluster.runningNodes().collect(Collectors.toSet()); + for (IgniteImpl node : nodes) { + assertTrue(waitForCondition(() -> isRestNotAvailable(node.restHttpAddress(), "", ""), Duration.ofSeconds(5).toMillis())); } // REST is available with valid credentials - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "ignite", "ignite")); + for (IgniteImpl node : nodes) { + assertTrue(isRestAvailable(node.restHttpAddress(), "ignite", "ignite")); } } @Test - public void changeCredentials(TestInfo testInfo) throws InterruptedException, IOException { - // When. - RestNode metaStorageNode = nodes.get(0); - - String initClusterBody = "{\n" - + " \"metaStorageNodes\": [\n" - + " \"" + metaStorageNode.name() + "\"\n" - + " ],\n" - + " \"cmgNodes\": [],\n" - + " \"clusterName\": \"cluster\",\n" - + " \"clusterConfiguration\": \"{" - + " security.enabled:true, " - + " security.authentication.providers:[{name:basic,password:password,type:basic,username:admin}]" - + " }\"\n" - + " }"; - - initCluster(metaStorageNode.httpAddress(), initClusterBody); + public void changeCredentials(TestInfo testInfo) throws Exception { + super.setup(testInfo); + Set<IgniteImpl> nodes = cluster.runningNodes().collect(Collectors.toSet()); // Then. // Authentication is enabled. - for (RestNode node : nodes) { - assertTrue(waitForCondition(() -> isRestNotAvailable(node.httpAddress(), "", ""), + for (IgniteImpl node : nodes) { + assertTrue(waitForCondition(() -> isRestNotAvailable(node.restHttpAddress(), "", ""), Duration.ofSeconds(5).toMillis())); } // REST is available with valid credentials - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "admin", "password")); + for (IgniteImpl node : nodes) { + assertTrue(isRestAvailable(node.restHttpAddress(), "admin", "password")); } // REST is not available with invalid credentials - for (RestNode node : nodes) { - assertFalse(isRestAvailable(node.httpAddress(), "admin", "wrong-password")); + for (IgniteImpl node : nodes) { + assertFalse(isRestAvailable(node.restHttpAddress(), "admin", "wrong-password")); } // Change credentials. - String updateRestAuthConfigBody = "{\n" - + " \"security\": {\n" - + " \"enabled\": true,\n" - + " \"authentication\": {\n" - + " \"providers\": [\n" - + " {\n" - + " \"name\": \"basic\",\n" - + " \"type\": \"basic\",\n" - + " \"username\": \"admin\",\n" - + " \"password\": \"new-password\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " }\n" - + "}"; - - updateClusterConfiguration(metaStorageNode.httpAddress(), "admin", "password", updateRestAuthConfigBody); + String updateRestAuthConfigBody = "security.authentication.providers.default.users.admin.password=new-password"; + + updateClusterConfiguration(node(0).restHttpAddress(), "admin", "password", updateRestAuthConfigBody); // REST is not available with old credentials - for (RestNode node : nodes) { - assertTrue(waitForCondition(() -> isRestNotAvailable(node.httpAddress(), "admin", "password"), + for (IgniteImpl node : nodes) { + assertTrue(waitForCondition(() -> isRestNotAvailable(node.restHttpAddress(), "admin", "password"), Duration.ofSeconds(5).toMillis())); } // REST is available with new credentials - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "admin", "new-password")); + for (IgniteImpl node : nodes) { + assertTrue(isRestAvailable(node.restHttpAddress(), "admin", "new-password")); } } @Test - public void enableAuthenticationAndRestartNode(TestInfo testInfo) throws InterruptedException, IOException { - // When. - RestNode metaStorageNode = nodes.get(0); - - String initClusterBody = "{\n" - + " \"metaStorageNodes\": [\n" - + " \"" + metaStorageNode.name() + "\"\n" - + " ],\n" - + " \"cmgNodes\": [],\n" - + " \"clusterName\": \"cluster\",\n" - + " \"clusterConfiguration\": \"{" - + " security.enabled:true, " - + " security.authentication.providers:[{name:basic,password:password,type:basic,username:admin}]}\"\n" - + " }"; - - initCluster(metaStorageNode.httpAddress(), initClusterBody); + public void enableAuthenticationAndRestartNode(TestInfo testInfo) throws Exception { + super.setup(testInfo); + Set<IgniteImpl> nodes = cluster.runningNodes().collect(Collectors.toSet()); // Then. // Authentication is enabled. - for (RestNode node : nodes) { - assertTrue(waitForCondition(() -> isRestNotAvailable(node.httpAddress(), "", ""), + for (IgniteImpl node : nodes) { + assertTrue(waitForCondition(() -> isRestNotAvailable(node.restHttpAddress(), "", ""), Duration.ofSeconds(5).toMillis())); } // REST is available with valid credentials - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "admin", "password")); + for (IgniteImpl node : nodes) { + assertTrue(isRestAvailable(node.restHttpAddress(), "admin", "password")); } // REST is not available with invalid credentials - for (RestNode node : nodes) { - assertFalse(isRestAvailable(node.httpAddress(), "admin", "wrong-password")); + for (IgniteImpl node : nodes) { + assertFalse(isRestAvailable(node.restHttpAddress(), "admin", "wrong-password")); } // Restart a node. - RestNode nodeToRestart = nodes.get(2); - nodeToRestart.restart(); - waitForAllNodesStarted(Collections.singletonList(nodeToRestart)); + restartNode(2); + + nodes = cluster.runningNodes().collect(Collectors.toSet()); // REST is available with valid credentials - for (RestNode node : nodes) { - assertTrue(isRestAvailable(node.httpAddress(), "admin", "password")); + for (IgniteImpl node : nodes) { + assertTrue(isRestAvailable(node.restHttpAddress(), "admin", "password")); } // REST is not available with invalid credentials - for (RestNode node : nodes) { - assertFalse(isRestAvailable(node.httpAddress(), "admin", "wrong-password")); + for (IgniteImpl node : nodes) { + assertFalse(isRestAvailable(node.restHttpAddress(), "admin", "wrong-password")); } } - private void initCluster(String baseUrl, String initClusterBody) throws IOException, InterruptedException { - URI clusterInitUri = URI.create(baseUrl + "/management/v1/cluster/init"); - HttpRequest initRequest = HttpRequest.newBuilder(clusterInitUri) - .header("content-type", "application/json") - .POST(BodyPublishers.ofString(initClusterBody)) - .build(); - - assertThat(sendRequest(client, initRequest), hasStatusCode(200)); - - waitForAllNodesStarted(nodes); - } - - private void updateClusterConfiguration(String baseUrl, String username, String password, String configToApply) { - URI updateClusterConfigUri = URI.create(baseUrl + "/management/v1/configuration/cluster/"); + private void updateClusterConfiguration(NetworkAddress address, String username, String password, String configToApply) { + URI updateClusterConfigUri = URI.create("http://" + hostUrl(address) + "/management/v1/configuration/cluster/"); HttpRequest updateClusterConfigRequest = HttpRequest.newBuilder(updateClusterConfigUri) .header("content-type", "text/plain") .header("Authorization", basicAuthenticationHeader(username, password)) @@ -278,12 +199,12 @@ public class ItAuthenticationTest extends BaseIgniteAbstractTest { assertThat(sendRequest(client, updateClusterConfigRequest), hasStatusCode(200)); } - private boolean isRestNotAvailable(String baseUrl, String username, String password) { + private boolean isRestNotAvailable(NetworkAddress baseUrl, String username, String password) { return !isRestAvailable(baseUrl, username, password); } - private boolean isRestAvailable(String baseUrl, String username, String password) { - URI clusterConfigUri = URI.create(baseUrl + "/management/v1/configuration/cluster/"); + private boolean isRestAvailable(NetworkAddress address, String username, String password) { + URI clusterConfigUri = URI.create("http://" + hostUrl(address) + "/management/v1/configuration/cluster/"); HttpRequest clusterConfigRequest = HttpRequest.newBuilder(clusterConfigUri) .header("Authorization", basicAuthenticationHeader(username, password)) .build(); @@ -294,7 +215,16 @@ public class ItAuthenticationTest extends BaseIgniteAbstractTest { } else if (code == 401) { return false; } else { - throw new IllegalStateException("Unexpected response code: " + code + ", body: " + response.body()); + throw new IllegalStateException("Unexpected response code: " + code + ", body: " + response.body()); + } + } + + private static String hostUrl(NetworkAddress networkAddress) { + try { + InetAddress host = InetAddress.getByName(networkAddress.host()); + return host.getHostAddress() + ":" + networkAddress.port(); + } catch (UnknownHostException e) { + throw new RuntimeException(e); } } @@ -306,17 +236,6 @@ public class ItAuthenticationTest extends BaseIgniteAbstractTest { } } - @AfterEach - void tearDown() { - nodes.stream().parallel().forEach(RestNode::stop); - } - - private static void waitForAllNodesStarted(List<RestNode> nodes) { - nodes.stream() - .map(RestNode::igniteNodeFuture) - .forEach(future -> assertThat(future, willCompleteSuccessfully())); - } - private static String basicAuthenticationHeader(String username, String password) { String valueToEncode = username + ":" + password; return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java index 575d9cb772..f93581611d 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java @@ -715,8 +715,9 @@ public class PlatformTestNodeRunner { if (enable) { change.changeProviders().create("basic", authenticationProviderChange -> { authenticationProviderChange.convert(BasicAuthenticationProviderChange.class) - .changeUsername("user-1") - .changePassword("password-1"); + .changeUsers(users -> + users.create("user-1", user -> user.changePassword("password-1")) + ); }); } } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java index 8d8673d5e3..59f1a316ea 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java @@ -17,7 +17,10 @@ package org.apache.ignite.internal.security.authentication; +import java.util.Objects; +import org.apache.ignite.configuration.NamedListView; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView; +import org.apache.ignite.internal.security.authentication.basic.BasicUserView; import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView; import org.jetbrains.annotations.Nullable; @@ -61,6 +64,23 @@ public class AuthenticationProviderEqualityVerifier { } private static boolean areEqual(BasicAuthenticationProviderView o1, BasicAuthenticationProviderView o2) { - return o1.username().equals(o2.username()) && o1.password().equals(o2.password()); + NamedListView<? extends BasicUserView> users1 = o1.users(); + NamedListView<? extends BasicUserView> users2 = o2.users(); + if (users1.size() != users2.size()) { + return false; + } + + for (BasicUserView basicUser1View : users1) { + BasicUserView basicUser2View = users2.get(basicUser1View.username()); + if (basicUser2View == null) { + return false; + } + if (!Objects.equals(basicUser1View.username(), basicUser2View.username()) + || !Objects.equals(basicUser1View.password(), basicUser2View.password())) { + return false; + } + } + + return true; } } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java index 441e703cfa..d9da4fc7b8 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java @@ -17,8 +17,10 @@ package org.apache.ignite.internal.security.authentication; +import java.util.stream.Collectors; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticator; +import org.apache.ignite.internal.security.authentication.basic.BasicUser; import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView; import org.apache.ignite.security.AuthenticationType; @@ -28,7 +30,11 @@ class AuthenticatorFactory { AuthenticationType type = AuthenticationType.parse(view.type()); if (type == AuthenticationType.BASIC) { BasicAuthenticationProviderView basicAuthProviderView = (BasicAuthenticationProviderView) view; - return new BasicAuthenticator(view.name(), basicAuthProviderView.username(), basicAuthProviderView.password()); + return new BasicAuthenticator( + view.name(), + basicAuthProviderView.users().stream().map(basicUserView -> new BasicUser(basicUserView.username(), + basicUserView.password())).collect(Collectors.toList()) + ); } else { throw new IllegalArgumentException("Unexpected authentication type: " + type); } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java index 31c5800b96..3b4a4cb7fc 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java @@ -28,7 +28,7 @@ import org.apache.ignite.configuration.annotation.ConfigurationType; import org.apache.ignite.configuration.validation.Validator; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema; -import org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl; +import org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl; import org.apache.ignite.internal.security.configuration.SecurityConfiguration; /** @@ -36,7 +36,7 @@ import org.apache.ignite.internal.security.configuration.SecurityConfiguration; */ @AutoService(ConfigurationModule.class) public class SecurityConfigurationModule implements ConfigurationModule { - private static final String DEFAULT_PROVIDER_NAME = "default"; + public static final String DEFAULT_PROVIDER_NAME = "default"; private static final String DEFAULT_USERNAME = "ignite"; @@ -66,11 +66,12 @@ public class SecurityConfigurationModule implements ConfigurationModule { public void patchConfigurationWithDynamicDefaults(SuperRootChange rootChange) { rootChange.changeRoot(SecurityConfiguration.KEY).changeAuthentication(authenticationChange -> { if (authenticationChange.changeProviders().size() == 0) { - authenticationChange.changeProviders().create(DEFAULT_PROVIDER_NAME, change -> { - change.convert(BasicAuthenticationProviderChange.class) - .changeUsername(DEFAULT_USERNAME) - .changePassword(DEFAULT_PASSWORD); - }); + authenticationChange.changeProviders().create(DEFAULT_PROVIDER_NAME, change -> + change.convert(BasicAuthenticationProviderChange.class) + .changeUsers(users -> users.create(DEFAULT_USERNAME, user -> + user.changePassword(DEFAULT_PASSWORD)) + ) + ); } }); } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java index de0f410d7c..762509754a 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java @@ -17,23 +17,13 @@ package org.apache.ignite.internal.security.authentication.basic; +import org.apache.ignite.configuration.annotation.NamedConfigValue; import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance; -import org.apache.ignite.configuration.annotation.Secret; -import org.apache.ignite.configuration.annotation.Value; -import org.apache.ignite.configuration.validation.NotBlank; import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema; /** Basic authentication configuration. */ @PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC) public class BasicAuthenticationProviderConfigurationSchema extends AuthenticationProviderConfigurationSchema { - /** Username. */ - @NotBlank - @Value - public String username; - - /** Password. */ - @Secret - @NotBlank - @Value - public String password; + @NamedConfigValue(syntheticKeyName = "username") + public BasicUserConfigurationSchema users; } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java index 9372867b61..f004b8d1ee 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java @@ -17,6 +17,11 @@ package org.apache.ignite.internal.security.authentication.basic; +import static java.util.function.Function.identity; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.apache.ignite.internal.security.authentication.AuthenticationRequest; import org.apache.ignite.internal.security.authentication.Authenticator; import org.apache.ignite.internal.security.authentication.UserDetails; @@ -26,23 +31,19 @@ import org.apache.ignite.security.exception.UnsupportedAuthenticationTypeExcepti /** Implementation of basic authenticator. */ public class BasicAuthenticator implements Authenticator { - private final String authenticatorName; - - private final String username; + private final String providerName; - private final String password; + private final Map<String, BasicUser> users; /** * Constructor. * - * @param authenticatorName Authenticator name. - * @param username Username. - * @param password Password. + * @param providerName Provider name. + * @param users List of registered users. */ - public BasicAuthenticator(String authenticatorName, String username, String password) { - this.authenticatorName = authenticatorName; - this.username = username; - this.password = password; + public BasicAuthenticator(String providerName, List<BasicUser> users) { + this.providerName = providerName; + this.users = users.stream().collect(Collectors.toMap(BasicUser::name, identity())); } @Override @@ -53,10 +54,16 @@ public class BasicAuthenticator implements Authenticator { ); } - if (username.equals(authenticationRequest.getIdentity()) && password.equals(authenticationRequest.getSecret())) { - return new UserDetails(username, authenticatorName); - } else { - throw new InvalidCredentialsException("Invalid credentials"); + Object requestUsername = authenticationRequest.getIdentity(); + Object requestPassword = authenticationRequest.getSecret(); + + BasicUser basicUser = users.get(requestUsername); + if (basicUser != null) { + if (basicUser.password().equals(requestPassword)) { + return new UserDetails(basicUser.name(), providerName); + } } + + throw new InvalidCredentialsException("Invalid credentials"); } } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java similarity index 53% copy from modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java copy to modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java index de0f410d7c..2b8b0900a5 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java @@ -17,23 +17,24 @@ package org.apache.ignite.internal.security.authentication.basic; -import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance; -import org.apache.ignite.configuration.annotation.Secret; -import org.apache.ignite.configuration.annotation.Value; -import org.apache.ignite.configuration.validation.NotBlank; -import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema; +/** + * Data class for user information. + */ +public class BasicUser { + private final String name; + + private final String password; + + public BasicUser(String name, String password) { + this.name = name; + this.password = password; + } -/** Basic authentication configuration. */ -@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC) -public class BasicAuthenticationProviderConfigurationSchema extends AuthenticationProviderConfigurationSchema { - /** Username. */ - @NotBlank - @Value - public String username; + public String name() { + return name; + } - /** Password. */ - @Secret - @NotBlank - @Value - public String password; + public String password() { + return password; + } } diff --git a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java similarity index 72% copy from modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java copy to modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java index de0f410d7c..69b18faecf 100644 --- a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java @@ -17,18 +17,19 @@ package org.apache.ignite.internal.security.authentication.basic; -import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance; +import org.apache.ignite.configuration.annotation.Config; +import org.apache.ignite.configuration.annotation.InjectedName; import org.apache.ignite.configuration.annotation.Secret; import org.apache.ignite.configuration.annotation.Value; import org.apache.ignite.configuration.validation.NotBlank; -import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema; -/** Basic authentication configuration. */ -@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC) -public class BasicAuthenticationProviderConfigurationSchema extends AuthenticationProviderConfigurationSchema { +/** + * Basic user configuration schema. + */ +@Config +public class BasicUserConfigurationSchema { /** Username. */ - @NotBlank - @Value + @InjectedName public String username; /** Password. */ diff --git a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java similarity index 52% rename from modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java rename to modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java index 84f6ccff3c..b6de765161 100644 --- a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java +++ b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java @@ -15,13 +15,18 @@ * limitations under the License. */ -package org.apache.ignite.internal.security.authentication.configuration.validator; +package org.apache.ignite.internal.security.authentication.validator; + +import static org.apache.ignite.internal.security.authentication.SecurityConfigurationModule.DEFAULT_PROVIDER_NAME; import org.apache.ignite.configuration.NamedListView; import org.apache.ignite.configuration.validation.ValidationContext; import org.apache.ignite.configuration.validation.ValidationIssue; import org.apache.ignite.configuration.validation.Validator; +import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfiguration; +import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView; import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView; +import org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidator; import org.apache.ignite.internal.security.configuration.SecurityConfiguration; /** @@ -39,8 +44,36 @@ public class AuthenticationProvidersValidatorImpl implements boolean enabled = ctx.getNewRoot(SecurityConfiguration.KEY).enabled(); NamedListView<? extends AuthenticationProviderView> view = ctx.getNewValue(); - if (enabled && view.size() == 0) { - ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Providers must be present, if security is enabled")); + BasicAuthenticationProviderView basicProvider = (BasicAuthenticationProviderView) view.get(DEFAULT_PROVIDER_NAME); + if (basicProvider == null) { + ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Default provider " + DEFAULT_PROVIDER_NAME + " is not removable.")); + return; + } + + if (!checkOnlyOneBasicProvider(ctx, view)) { + return; + } + + if (enabled && view.size() == 1 && basicProvider.users().size() == 0) { + ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Basic provider must have at least one user " + + "in case when no other providers present.")); + } + } + + private static boolean checkOnlyOneBasicProvider( + ValidationContext<NamedListView<? extends AuthenticationProviderView>> ctx, + NamedListView<? extends AuthenticationProviderView> view + ) { + boolean basicAlreadyFound = false; + for (AuthenticationProviderView authenticationProviderView : view) { + if (authenticationProviderView instanceof BasicAuthenticationProviderConfiguration) { + if (basicAlreadyFound) { + ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Only one basic provider supported.")); + return false; + } + basicAlreadyFound = true; + } } + return true; } } diff --git a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java index 92b1e21c06..5a941be645 100644 --- a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java +++ b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java @@ -187,8 +187,9 @@ class AuthenticationManagerImplTest extends BaseIgniteAbstractTest { securityConfiguration, change -> { change.changeAuthentication().changeProviders(providers -> providers.update(PROVIDER, provider -> { provider.convert(BasicAuthenticationProviderChange.class) - .changeUsername(USERNAME) - .changePassword("new-password"); + .changeUsers(users -> + users.update(USERNAME, user -> user.changePassword("new-password")) + ); })); }) .value(); @@ -244,8 +245,9 @@ class AuthenticationManagerImplTest extends BaseIgniteAbstractTest { securityConfiguration, change -> { change.changeAuthentication().changeProviders(providers -> providers.create(PROVIDER, provider -> { provider.convert(BasicAuthenticationProviderChange.class) - .changeUsername(USERNAME) - .changePassword(PASSWORD); + .changeUsers(users -> + users.create(USERNAME, user -> user.changePassword(PASSWORD)) + ); })); change.changeEnabled(true); }) diff --git a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java index 522d793a0b..5f0fccea18 100644 --- a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java +++ b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java @@ -19,6 +19,7 @@ package org.apache.ignite.internal.security.authentication; import static org.apache.ignite.internal.configuration.validation.TestValidationUtil.mockValidationContext; import static org.apache.ignite.internal.configuration.validation.TestValidationUtil.validate; +import static org.apache.ignite.internal.security.authentication.SecurityConfigurationModule.DEFAULT_PROVIDER_NAME; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -30,7 +31,7 @@ import org.apache.ignite.internal.configuration.testframework.ConfigurationExten import org.apache.ignite.internal.configuration.testframework.InjectConfiguration; import org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView; import org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidator; -import org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl; +import org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl; import org.apache.ignite.internal.security.configuration.SecurityChange; import org.apache.ignite.internal.security.configuration.SecurityConfiguration; import org.apache.ignite.internal.security.configuration.SecurityView; @@ -62,7 +63,7 @@ class AuthenticationProvidersValidatorImplTest extends BaseIgniteAbstractTest { AuthenticationProvidersValidatorImpl.INSTANCE, mock(AuthenticationProvidersValidator.class), ctx, - "Providers must be present, if security is enabled" + "Default provider " + DEFAULT_PROVIDER_NAME + " is not removable" ); } diff --git a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java index ec51859953..17b354270b 100644 --- a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java +++ b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java @@ -34,7 +34,7 @@ import org.apache.ignite.internal.configuration.SuperRootChangeImpl; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema; import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView; -import org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl; +import org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl; import org.apache.ignite.internal.security.configuration.SecurityConfiguration; import org.apache.ignite.internal.security.configuration.SecurityView; import org.junit.jupiter.api.AfterEach; @@ -107,8 +107,8 @@ class SecurityConfigurationModuleTest { .get(0); assertAll( - () -> assertThat(defaultProvider.username(), equalTo("ignite")), - () -> assertThat(defaultProvider.password(), equalTo("ignite")) + () -> assertThat(defaultProvider.users().get(0).username(), equalTo("ignite")), + () -> assertThat(defaultProvider.users().get(0).password(), equalTo("ignite")) ); } @@ -117,8 +117,7 @@ class SecurityConfigurationModuleTest { rootChange.changeRoot(SecurityConfiguration.KEY).changeAuthentication(authenticationChange -> { authenticationChange.changeProviders().create("basic", change -> { change.convert(BasicAuthenticationProviderChange.class) - .changeUsername("admin") - .changePassword("password"); + .changeUsers(users -> users.create("admin", user -> user.changePassword("password"))); }); }); @@ -133,8 +132,8 @@ class SecurityConfigurationModuleTest { .get(0); assertAll( - () -> assertThat(defaultProvider.username(), equalTo("admin")), - () -> assertThat(defaultProvider.password(), equalTo("password")) + () -> assertThat(defaultProvider.users().get(0).username(), equalTo("admin")), + () -> assertThat(defaultProvider.users().get(0).password(), equalTo("password")) ); } } diff --git a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java index 3a2aca2b78..78f616799b 100644 --- a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java +++ b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java @@ -21,25 +21,25 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Field; import java.util.Arrays; +import org.apache.ignite.configuration.annotation.InjectedName; import org.apache.ignite.configuration.annotation.Secret; import org.apache.ignite.configuration.validation.NotBlank; -import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema; import org.junit.jupiter.api.Test; class BasicAuthenticationProviderConfigurationSchemaTest { @Test - public void usernameIsNotBlank() { - Field username = Arrays.stream(BasicAuthenticationProviderConfigurationSchema.class.getDeclaredFields()) + public void usernameIsInjected() { + Field username = Arrays.stream(BasicUserConfigurationSchema.class.getDeclaredFields()) .filter(it -> it.getName().equals("username")) .findFirst() .orElseThrow(); - assertTrue(username.isAnnotationPresent(NotBlank.class)); + assertTrue(username.isAnnotationPresent(InjectedName.class)); } @Test public void passwordIsSecretAndNotBlank() { - Field password = Arrays.stream(BasicAuthenticationProviderConfigurationSchema.class.getDeclaredFields()) + Field password = Arrays.stream(BasicUserConfigurationSchema.class.getDeclaredFields()) .filter(it -> it.getName().equals("password")) .findFirst() .orElseThrow(); diff --git a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java index 7fe9cce0bd..2f4bce91c5 100644 --- a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java +++ b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.security.authentication.basic; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.List; import org.apache.ignite.internal.security.authentication.AnonymousRequest; import org.apache.ignite.internal.security.authentication.UserDetails; import org.apache.ignite.internal.security.authentication.UsernamePasswordRequest; @@ -28,7 +29,10 @@ import org.apache.ignite.security.exception.UnsupportedAuthenticationTypeExcepti import org.junit.jupiter.api.Test; class BasicAuthenticatorTest { - private final BasicAuthenticator authenticator = new BasicAuthenticator("basic", "admin", "password"); + private final BasicAuthenticator authenticator = new BasicAuthenticator( + "basic", + List.of(new BasicUser("admin", "password")) + ); @Test void authenticate() {