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 13e0425005 IGNITE-20647 Enhance authentication events handling in
ClientInboundMessageHandler (#2688)
13e0425005 is described below
commit 13e0425005d4d9dfca63e7e14c117ec0a8790f1f
Author: Ivan Gagarkin <[email protected]>
AuthorDate: Tue Oct 31 23:17:14 2023 +0700
IGNITE-20647 Enhance authentication events handling in
ClientInboundMessageHandler (#2688)
* If a user's password changes, only that user should be disconnected.
* If authentication i`s enabled, all currently connected users should be
disconnected.
* In all other cases, existing connections should remain intact.
---
modules/client-handler/build.gradle | 5 +-
.../apache/ignite/client/handler/TestServer.java | 1 -
.../ignite/client/handler/ClientHandlerModule.java | 21 +-
.../handler/ClientInboundMessageHandler.java | 49 ++--
.../handler/ClientInboundMessageHandlerTest.java | 326 +++++++++++++++++++++
.../java/org/apache/ignite/client/TestServer.java | 1 -
.../Apache.Ignite.Tests/BasicAuthenticatorTests.cs | 7 +-
.../org/apache/ignite/internal/app/IgniteImpl.java | 3 -
.../authentication/AuthenticationManager.java | 14 +
.../security/authentication/UserDetails.java | 9 +-
.../AuthenticationEvent.java} | 21 +-
.../AuthenticationListener.java} | 19 +-
.../event/AuthenticationProviderEvent.java | 52 ++++
.../{UserDetails.java => event/EventType.java} | 19 +-
modules/security/build.gradle | 1 +
.../authentication/AuthenticationManagerImpl.java | 83 +++++-
.../AuthenticationProviderEqualityVerifier.java | 66 +++++
.../authentication/AuthenticatorFactory.java | 2 +-
.../authentication/basic/BasicAuthenticator.java | 14 +-
.../AuthenticationManagerImplTest.java | 171 ++++++-----
.../basic/BasicAuthenticatorTest.java | 3 +-
21 files changed, 722 insertions(+), 165 deletions(-)
diff --git a/modules/client-handler/build.gradle
b/modules/client-handler/build.gradle
index 1f178a0294..0bf68e56a2 100644
--- a/modules/client-handler/build.gradle
+++ b/modules/client-handler/build.gradle
@@ -33,7 +33,7 @@ dependencies {
implementation project(':ignite-network')
implementation project(':ignite-core')
implementation project(':ignite-schema')
- implementation project(':ignite-security-api')
+ implementation project(':ignite-security')
implementation project(':ignite-metrics')
implementation project(':ignite-transactions')
implementation project(':ignite-catalog')
@@ -48,9 +48,12 @@ dependencies {
implementation libs.auto.service.annotations
testImplementation project(':ignite-configuration')
+ testImplementation project(':ignite-security')
testImplementation(testFixtures(project(':ignite-core')))
+ testImplementation(testFixtures(project(':ignite-configuration')))
testImplementation libs.mockito.junit
testImplementation libs.hamcrest.core
+ testImplementation libs.awaitility
integrationTestImplementation project(':ignite-core')
integrationTestImplementation project(':ignite-api')
diff --git
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
index ce856020f2..67b4a4d3b0 100644
---
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
+++
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/TestServer.java
@@ -131,7 +131,6 @@ public class TestServer {
mock(MetricManager.class),
metrics,
authenticationManager(),
- securityConfiguration,
new HybridClockImpl(),
new AlwaysSyncedSchemaSyncService(),
mock(CatalogService.class)
diff --git
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
index da9cce76c7..bc6356483d 100644
---
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
+++
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientHandlerModule.java
@@ -46,7 +46,6 @@ import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.metrics.MetricManager;
import org.apache.ignite.internal.network.ssl.SslContextProvider;
import
org.apache.ignite.internal.security.authentication.AuthenticationManager;
-import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
import org.apache.ignite.internal.sql.engine.QueryProcessor;
import org.apache.ignite.internal.table.IgniteTablesInternal;
import org.apache.ignite.internal.table.distributed.schema.SchemaSyncService;
@@ -104,8 +103,6 @@ public class ClientHandlerModule implements IgniteComponent
{
private final AuthenticationManager authenticationManager;
- private final SecurityConfiguration securityConfiguration;
-
private final HybridClock clock;
private final SchemaSyncService schemaSyncService;
@@ -126,7 +123,6 @@ public class ClientHandlerModule implements IgniteComponent
{
* @param clusterIdSupplier ClusterId supplier.
* @param metricManager Metric manager.
* @param authenticationManager Authentication manager.
- * @param securityConfiguration Security configuration.
* @param clock Hybrid clock.
*/
public ClientHandlerModule(
@@ -142,7 +138,6 @@ public class ClientHandlerModule implements IgniteComponent
{
MetricManager metricManager,
ClientHandlerMetricSource metrics,
AuthenticationManager authenticationManager,
- SecurityConfiguration securityConfiguration,
HybridClock clock,
SchemaSyncService schemaSyncService,
CatalogService catalogService
@@ -158,7 +153,6 @@ public class ClientHandlerModule implements IgniteComponent
{
assert metricManager != null;
assert metrics != null;
assert authenticationManager != null;
- assert securityConfiguration != null;
assert clock != null;
assert schemaSyncService != null;
assert catalogService != null;
@@ -175,7 +169,6 @@ public class ClientHandlerModule implements IgniteComponent
{
this.metricManager = metricManager;
this.metrics = metrics;
this.authenticationManager = authenticationManager;
- this.securityConfiguration = securityConfiguration;
this.clock = clock;
this.schemaSyncService = schemaSyncService;
this.catalogService = catalogService;
@@ -264,9 +257,17 @@ public class ClientHandlerModule implements
IgniteComponent {
ch.pipeline().addFirst("ssl",
sslContext.newHandler(ch.alloc()));
}
+ ClientInboundMessageHandler messageHandler =
createInboundMessageHandler(configuration, clusterId);
+ authenticationManager.listen(messageHandler);
+
ch.pipeline().addLast(
new ClientMessageDecoder(),
- createInboundMessageHandler(configuration,
clusterId));
+ messageHandler
+ );
+
+ ch.closeFuture().addListener(future -> {
+ authenticationManager.stopListen(messageHandler);
+ });
metrics.connectionsInitiatedIncrement();
}
@@ -303,7 +304,7 @@ public class ClientHandlerModule implements IgniteComponent
{
}
private ClientInboundMessageHandler
createInboundMessageHandler(ClientConnectorView configuration,
CompletableFuture<UUID> clusterId) {
- ClientInboundMessageHandler clientInboundMessageHandler = new
ClientInboundMessageHandler(
+ return new ClientInboundMessageHandler(
igniteTables,
igniteTransactions,
queryProcessor,
@@ -318,8 +319,6 @@ public class ClientHandlerModule implements IgniteComponent
{
schemaSyncService,
catalogService
);
- securityConfiguration.listen(clientInboundMessageHandler);
- return clientInboundMessageHandler;
}
}
diff --git
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
index e97467ec3d..2b1ff348c8 100644
---
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
+++
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
@@ -80,8 +80,6 @@ import
org.apache.ignite.client.handler.requests.tx.ClientTransactionBeginReques
import
org.apache.ignite.client.handler.requests.tx.ClientTransactionCommitRequest;
import
org.apache.ignite.client.handler.requests.tx.ClientTransactionRollbackRequest;
import org.apache.ignite.compute.IgniteCompute;
-import org.apache.ignite.configuration.notifications.ConfigurationListener;
-import
org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
import org.apache.ignite.internal.catalog.CatalogService;
import org.apache.ignite.internal.client.proto.ClientMessageCommon;
import org.apache.ignite.internal.client.proto.ClientMessagePacker;
@@ -105,7 +103,9 @@ import
org.apache.ignite.internal.security.authentication.AuthenticationManager;
import
org.apache.ignite.internal.security.authentication.AuthenticationRequest;
import org.apache.ignite.internal.security.authentication.UserDetails;
import
org.apache.ignite.internal.security.authentication.UsernamePasswordRequest;
-import org.apache.ignite.internal.security.configuration.SecurityView;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationEvent;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationListener;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationProviderEvent;
import org.apache.ignite.internal.sql.engine.QueryProcessor;
import org.apache.ignite.internal.table.IgniteTablesInternal;
import org.apache.ignite.internal.table.distributed.schema.SchemaSyncService;
@@ -126,7 +126,7 @@ import org.jetbrains.annotations.Nullable;
* Handles messages from thin clients.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
-public class ClientInboundMessageHandler extends ChannelInboundHandlerAdapter
implements ConfigurationListener<SecurityView> {
+public class ClientInboundMessageHandler extends ChannelInboundHandlerAdapter
implements AuthenticationListener {
/** The logger. */
private static final IgniteLogger LOG =
Loggers.forClass(ClientInboundMessageHandler.class);
@@ -302,7 +302,8 @@ public class ClientInboundMessageHandler extends
ChannelInboundHandlerAdapter im
var features = BitSet.valueOf(unpacker.readPayload(featuresLen));
Map<HandshakeExtension, Object> extensions =
extractExtensions(unpacker);
- UserDetails userDetails = authenticate(extensions);
+ AuthenticationRequest<?, ?> authenticationRequest =
createAuthenticationRequest(extensions);
+ UserDetails userDetails =
authenticationManager.authenticate(authenticationRequest);
clientContext = new ClientContext(clientVer, clientCode, features,
userDetails);
@@ -356,12 +357,6 @@ public class ClientInboundMessageHandler extends
ChannelInboundHandlerAdapter im
}
}
- private UserDetails authenticate(Map<HandshakeExtension, Object>
extensions) {
- AuthenticationRequest<?, ?> authenticationRequest =
createAuthenticationRequest(extensions);
-
- return authenticationManager.authenticate(authenticationRequest);
- }
-
private static AuthenticationRequest<?, ?>
createAuthenticationRequest(Map<HandshakeExtension, Object> extensions) {
Object authnType =
extensions.get(HandshakeExtension.AUTHENTICATION_TYPE);
@@ -719,14 +714,6 @@ public class ClientInboundMessageHandler extends
ChannelInboundHandlerAdapter im
ctx.close();
}
- @Override
- public CompletableFuture<?>
onUpdate(ConfigurationNotificationEvent<SecurityView> ctx) {
- if (clientContext != null && channelHandlerContext != null) {
- channelHandlerContext.close();
- }
- return CompletableFuture.completedFuture(null);
- }
-
private static Map<HandshakeExtension, Object>
extractExtensions(ClientMessageUnpacker unpacker) {
EnumMap<HandshakeExtension, Object> extensions = new
EnumMap<>(HandshakeExtension.class);
int mapSize = unpacker.unpackInt();
@@ -772,4 +759,28 @@ public class ClientInboundMessageHandler extends
ChannelInboundHandlerAdapter im
return clock.now().longValue();
}
+
+ @Override
+ public void onEvent(AuthenticationEvent event) {
+ switch (event.type()) {
+ case AUTHENTICATION_ENABLED:
+ closeConnection();
+ break;
+ case AUTHENTICATION_PROVIDER_REMOVED:
+ case AUTHENTICATION_PROVIDER_UPDATED:
+ AuthenticationProviderEvent providerEvent =
(AuthenticationProviderEvent) event;
+ if (clientContext != null &&
clientContext.userDetails().providerName().equals(providerEvent.name())) {
+ closeConnection();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void closeConnection() {
+ if (channelHandlerContext != null) {
+ channelHandlerContext.close();
+ }
+ }
}
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
new file mode 100644
index 0000000000..1e311ffdd2
--- /dev/null
+++
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.client.handler;
+
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import
org.apache.ignite.client.handler.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.compute.IgniteCompute;
+import org.apache.ignite.internal.catalog.CatalogService;
+import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
+import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
+import
org.apache.ignite.internal.security.authentication.AuthenticationManager;
+import
org.apache.ignite.internal.security.authentication.AuthenticationManagerImpl;
+import
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange;
+import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
+import org.apache.ignite.internal.sql.engine.QueryProcessor;
+import org.apache.ignite.internal.table.IgniteTablesInternal;
+import org.apache.ignite.internal.table.distributed.schema.SchemaSyncService;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.tx.impl.IgniteTransactionsImpl;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.network.ClusterNodeImpl;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.TopologyService;
+import org.apache.ignite.sql.IgniteSql;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.msgpack.core.MessagePack;
+
+@ExtendWith(MockitoExtension.class)
+@ExtendWith(ConfigurationExtension.class)
+class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest {
+ private static final Duration TIMEOUT_OF_DURING = Duration.ofSeconds(2);
+
+ @InjectConfiguration
+ private ClientConnectorConfiguration configuration;
+
+ @InjectConfiguration
+ private SecurityConfiguration securityConfiguration;
+
+ @Mock
+ private IgniteTablesInternal igniteTables;
+
+ @Mock
+ private IgniteTransactionsImpl igniteTransactions;
+
+ @Mock
+ private QueryProcessor processor;
+
+ @Mock
+ private IgniteCompute compute;
+
+ @Mock
+ private TopologyService topologyService;
+
+ @Mock
+ private ClusterService clusterService;
+
+ @Mock
+ private IgniteSql sql;
+
+ @Mock
+ private CompletableFuture<UUID> clusterId;
+
+ @Mock
+ private ClientHandlerMetricSource metrics;
+
+ @Mock
+ private HybridClock clock;
+
+ @Mock
+ private SchemaSyncService schemaSyncService;
+
+ @Mock
+ private CatalogService catalogService;
+
+ @Mock
+ private ChannelHandlerContext ctx;
+
+ @Mock
+ private Channel channel;
+
+ @Mock
+ private ChannelFuture channelFuture;
+
+ private ClientInboundMessageHandler handler;
+
+ private final AtomicBoolean ctxClosed = new AtomicBoolean(false);
+
+ @BeforeEach
+ void setUp() throws Exception {
+ doReturn(topologyService).when(clusterService).topologyService();
+
+ ClusterNode node = new ClusterNodeImpl("node1", "node1", new
NetworkAddress("localhost", 10800));
+ doReturn(node).when(topologyService).localMember();
+
+ doReturn(UUID.randomUUID()).when(clusterId).join();
+
+ doReturn(channelFuture).when(channel).closeFuture();
+
+ doReturn(new UnpooledByteBufAllocator(true)).when(ctx).alloc();
+ doReturn(channel).when(ctx).channel();
+ lenient().doAnswer(invocation -> {
+ ctxClosed.set(true);
+ return null;
+ }).when(ctx).close();
+
+ AuthenticationManager authenticationManager = new
AuthenticationManagerImpl();
+
+ handler = new ClientInboundMessageHandler(
+ igniteTables,
+ igniteTransactions,
+ processor,
+ configuration.value(),
+ compute,
+ clusterService,
+ sql,
+ clusterId,
+ metrics,
+ authenticationManager,
+ clock,
+ schemaSyncService,
+ catalogService
+ );
+
+ authenticationManager.listen(handler);
+ securityConfiguration.listen(authenticationManager);
+
+ 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 -> {
+
basicChange.convert(BasicAuthenticationProviderChange.class)
+ .changeUsername("admin1")
+ .changePassword("password");
+ });
+ });
+ });
+ }).join();
+
+ handler.channelRegistered(ctx);
+ }
+
+ @Test
+ void disableAuthentication() throws IOException {
+ handshake();
+
+ securityConfiguration.change(change -> {
+ change.changeEnabled(false);
+ }).join();
+
+ await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
+ }
+
+ @Test
+ void enableAuthentication() throws InterruptedException, IOException {
+ securityConfiguration.change(change -> {
+ change.changeEnabled(false);
+ }).join();
+
+ handshake();
+
+ securityConfiguration.change(change -> {
+ change.changeEnabled(true);
+ }).join();
+
+ await().untilAtomic(ctxClosed, is(true));
+ }
+
+ @Test
+ void changeCurrentProvider() throws IOException {
+ handshake();
+
+ 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();
+
+ await().untilAtomic(ctxClosed, is(true));
+ }
+
+ @Test
+ void changeAnotherProvider() throws IOException {
+ handshake();
+
+ securityConfiguration.change(change -> {
+ change.changeEnabled(true);
+ change.changeAuthentication(authChange -> {
+ authChange.changeProviders(providersChange -> {
+ providersChange.update("basic1", basicChange -> {
+
basicChange.convert(BasicAuthenticationProviderChange.class)
+ .changeUsername("admin1")
+ .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();
+
+ await().untilAtomic(ctxClosed, is(true));
+ }
+
+ @Test
+ void deleteAnotherProvider() throws IOException {
+ handshake();
+
+ securityConfiguration.change(change -> {
+ change.changeEnabled(true);
+ change.changeAuthentication(authChange -> {
+ authChange.changeProviders(providersChange -> {
+ providersChange.delete("basic1");
+ });
+ });
+ }).join();
+
+ await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
+ }
+
+ @Test
+ void createNewProvider() throws IOException {
+ handshake();
+
+ securityConfiguration.change(change -> {
+ change.changeEnabled(true);
+ change.changeAuthentication(authChange -> {
+ authChange.changeProviders(providersChange -> {
+ providersChange.create("basic2", basicChange -> {
+
basicChange.convert(BasicAuthenticationProviderChange.class)
+ .changeUsername("admin2")
+ .changePassword("admin");
+ });
+ });
+ });
+ }).join();
+
+ await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
+ }
+
+ private void handshake() throws IOException {
+ var packer = MessagePack.newDefaultBufferPacker();
+ packer.packInt(3); // Major.
+ packer.packInt(0); // Minor.
+ packer.packInt(0); // Patch.
+
+ packer.packInt(2); // Client type: general purpose.
+
+ packer.packBinaryHeader(0); // Features.
+ packer.packInt(3); // Extensions.
+ packer.packString("authn-type");
+ packer.packString("basic");
+ packer.packString("authn-identity");
+ packer.packString("admin");
+ packer.packString("authn-secret");
+ packer.packString("password");
+
+ ByteBuf byteBuf = Unpooled.wrappedBuffer(packer.toByteArray());
+
+ handler.channelRead(ctx, byteBuf);
+
+ verify(ctx).writeAndFlush(any());
+ }
+}
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
b/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
index 0ca2588ea9..fe1ae61412 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/TestServer.java
@@ -225,7 +225,6 @@ public class TestServer implements AutoCloseable {
mock(MetricManager.class),
metrics,
authenticationManager(securityConfigurationOnInit),
- securityConfigurationOnInit,
clock,
new AlwaysSyncedSchemaSyncService(),
mockCatalogService()
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/BasicAuthenticatorTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/BasicAuthenticatorTests.cs
index 959ca32417..47af9b2b07 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/BasicAuthenticatorTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/BasicAuthenticatorTests.cs
@@ -112,8 +112,11 @@ public class BasicAuthenticatorTests : IgniteTestsBase
// As a result of this call, the client may be disconnected from
the server due to authn config change.
}
- // Wait for the server to apply the configuration change and drop the
client connection.
- client.WaitForConnections(0, 3000);
+ if (enable)
+ {
+ // Wait for the server to apply the configuration change and drop
the client connection.
+ client.WaitForConnections(0, 3000);
+ }
_authnEnabled = enable;
}
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 4e4c870fee..fc67ac5fed 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -655,8 +655,6 @@ public class IgniteImpl implements Ignite {
authenticationManager = createAuthenticationManager();
- SecurityConfiguration securityConfiguration =
clusterConfigRegistry.getConfiguration(SecurityConfiguration.KEY);
-
clientHandlerModule = new ClientHandlerModule(
qryEngine,
distributedTblMgr,
@@ -671,7 +669,6 @@ public class IgniteImpl implements Ignite {
metricManager,
new ClientHandlerMetricSource(),
authenticationManager,
- securityConfiguration,
clock,
schemaSyncService,
catalogManager
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManager.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManager.java
index 89331b4999..defc291f87 100644
---
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManager.java
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManager.java
@@ -18,10 +18,24 @@
package org.apache.ignite.internal.security.authentication;
import org.apache.ignite.configuration.notifications.ConfigurationListener;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationListener;
import org.apache.ignite.internal.security.configuration.SecurityView;
/**
* Authentication manager.
*/
public interface AuthenticationManager extends Authenticator,
ConfigurationListener<SecurityView> {
+ /**
+ * Listen to authentication events.
+ *
+ * @param listener Listener.
+ */
+ void listen(AuthenticationListener listener);
+
+ /**
+ * Stop listen to authentication events.
+ *
+ * @param listener Listener.
+ */
+ void stopListen(AuthenticationListener listener);
}
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
index 8fa797ce69..e2a938d128 100644
---
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
@@ -23,11 +23,18 @@ package org.apache.ignite.internal.security.authentication;
public class UserDetails {
private final String username;
- public UserDetails(String username) {
+ private final String providerName;
+
+ public UserDetails(String username, String providerName) {
this.username = username;
+ this.providerName = providerName;
}
public String username() {
return username;
}
+
+ public String providerName() {
+ return providerName;
+ }
}
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationEvent.java
similarity index 73%
copy from
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
copy to
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationEvent.java
index 8fa797ce69..04514c37e3 100644
---
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationEvent.java
@@ -15,19 +15,16 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.security.authentication;
+package org.apache.ignite.internal.security.authentication.event;
/**
- * Represents the user details.
+ * Represents the authentication event.
*/
-public class UserDetails {
- private final String username;
-
- public UserDetails(String username) {
- this.username = username;
- }
-
- public String username() {
- return username;
- }
+public interface AuthenticationEvent {
+ /**
+ * Returns the event type.
+ *
+ * @return the event type.
+ */
+ EventType type();
}
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationListener.java
similarity index 73%
copy from
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
copy to
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationListener.java
index 8fa797ce69..1f6178ad75 100644
---
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationListener.java
@@ -15,19 +15,14 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.security.authentication;
+package org.apache.ignite.internal.security.authentication.event;
/**
- * Represents the user details.
+ * Authentication events listener.
*/
-public class UserDetails {
- private final String username;
-
- public UserDetails(String username) {
- this.username = username;
- }
-
- public String username() {
- return username;
- }
+public interface AuthenticationListener {
+ /**
+ * Handle authentication event.
+ */
+ void onEvent(AuthenticationEvent event);
}
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationProviderEvent.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationProviderEvent.java
new file mode 100644
index 0000000000..2484921233
--- /dev/null
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/AuthenticationProviderEvent.java
@@ -0,0 +1,52 @@
+/*
+ * 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.security.authentication.event;
+
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_PROVIDER_REMOVED;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_PROVIDER_UPDATED;
+
+/**
+ * Represents the authentication provider event.
+ */
+public class AuthenticationProviderEvent implements AuthenticationEvent {
+ private final EventType type;
+
+ private final String name;
+
+ private AuthenticationProviderEvent(EventType type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ public static AuthenticationProviderEvent updated(String name) {
+ return new
AuthenticationProviderEvent(AUTHENTICATION_PROVIDER_UPDATED, name);
+ }
+
+ public static AuthenticationProviderEvent removed(String name) {
+ return new
AuthenticationProviderEvent(AUTHENTICATION_PROVIDER_REMOVED, name);
+ }
+
+ @Override
+ public EventType type() {
+ return type;
+ }
+
+ public String name() {
+ return name;
+ }
+}
diff --git
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/EventType.java
similarity index 73%
copy from
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
copy to
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/EventType.java
index 8fa797ce69..0418a7a9ee 100644
---
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/UserDetails.java
+++
b/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/event/EventType.java
@@ -15,19 +15,14 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.security.authentication;
+package org.apache.ignite.internal.security.authentication.event;
/**
- * Represents the user details.
+ * Represents the authentication event type.
*/
-public class UserDetails {
- private final String username;
-
- public UserDetails(String username) {
- this.username = username;
- }
-
- public String username() {
- return username;
- }
+public enum EventType {
+ AUTHENTICATION_ENABLED,
+ AUTHENTICATION_DISABLED,
+ AUTHENTICATION_PROVIDER_REMOVED,
+ AUTHENTICATION_PROVIDER_UPDATED
}
diff --git a/modules/security/build.gradle b/modules/security/build.gradle
index 1eb774e518..634c258117 100644
--- a/modules/security/build.gradle
+++ b/modules/security/build.gradle
@@ -37,6 +37,7 @@ dependencies {
testImplementation testFixtures(project(':ignite-configuration'))
testImplementation libs.typesafe.config
testImplementation libs.mockito.core
+ testImplementation libs.mockito.junit
testImplementation libs.hamcrest.core
testImplementation libs.hamcrest.optional
}
diff --git
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
index 440558a7c5..420cddaa9d 100644
---
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
+++
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
@@ -17,10 +17,14 @@
package org.apache.ignite.internal.security.authentication;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_DISABLED;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_ENABLED;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@@ -30,6 +34,9 @@ import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
import
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
import
org.apache.ignite.internal.security.authentication.configuration.AuthenticationView;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationEvent;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationListener;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationProviderEvent;
import org.apache.ignite.internal.security.configuration.SecurityView;
import org.apache.ignite.security.exception.InvalidCredentialsException;
import
org.apache.ignite.security.exception.UnsupportedAuthenticationTypeException;
@@ -44,6 +51,8 @@ public class AuthenticationManagerImpl implements
AuthenticationManager {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
+ private final List<AuthenticationListener> listeners = new
CopyOnWriteArrayList<>();
+
private List<Authenticator> authenticators = new ArrayList<>();
private boolean authEnabled = false;
@@ -62,7 +71,7 @@ public class AuthenticationManagerImpl implements
AuthenticationManager {
.findFirst()
.orElseThrow(() -> new
InvalidCredentialsException("Authentication failed"));
} else {
- return new UserDetails("Unknown");
+ return new UserDetails("Unknown", "Unknown");
}
} finally {
rwLock.readLock().unlock();
@@ -82,12 +91,15 @@ public class AuthenticationManagerImpl implements
AuthenticationManager {
}
@Override
- public CompletableFuture<?> onUpdate(
- ConfigurationNotificationEvent<SecurityView> ctx) {
- return CompletableFuture.runAsync(() ->
refreshProviders(ctx.newValue()));
+ public CompletableFuture<?>
onUpdate(ConfigurationNotificationEvent<SecurityView> ctx) {
+ if (refreshProviders(ctx.newValue())) {
+ emitEvents(ctx);
+ }
+
+ return CompletableFuture.completedFuture(null);
}
- private void refreshProviders(@Nullable SecurityView view) {
+ private boolean refreshProviders(@Nullable SecurityView view) {
rwLock.writeLock().lock();
try {
if (view == null || !view.enabled()) {
@@ -98,9 +110,15 @@ public class AuthenticationManagerImpl implements
AuthenticationManager {
authEnabled = true;
} else {
LOG.error("Invalid configuration: security is enabled, but no
providers. Leaving the old settings");
+
+ return false;
}
+
+ return true;
} catch (Exception exception) {
LOG.error("Couldn't refresh authentication providers. Leaving the
old settings", exception);
+
+ return false;
} finally {
rwLock.writeLock().unlock();
}
@@ -114,6 +132,61 @@ public class AuthenticationManagerImpl implements
AuthenticationManager {
.collect(Collectors.toList());
}
+ private void emitEvents(ConfigurationNotificationEvent<SecurityView> ctx) {
+ SecurityView oldValue = ctx.oldValue();
+ SecurityView newValue = ctx.newValue();
+
+ // Authentication enabled/disabled.
+ if ((oldValue == null || oldValue.enabled()) && !newValue.enabled()) {
+ notifyListeners(() -> AUTHENTICATION_DISABLED);
+ } else if ((oldValue == null || !oldValue.enabled()) &&
newValue.enabled()) {
+ notifyListeners(() -> AUTHENTICATION_ENABLED);
+ }
+
+ if (oldValue != null) {
+ // Authentication providers removed.
+ oldValue.authentication()
+ .providers()
+ .stream()
+ .map(AuthenticationProviderView::name)
+ .filter(it ->
newValue.authentication().providers().get(it) == null)
+ .map(AuthenticationProviderEvent::removed)
+ .forEach(this::notifyListeners);
+
+ // Authentication providers updated.
+ oldValue.authentication()
+ .providers()
+ .stream()
+ .filter(oldProvider -> {
+ AuthenticationProviderView newProvider =
newValue.authentication().providers().get(oldProvider.name());
+ return newProvider != null &&
!AuthenticationProviderEqualityVerifier.areEqual(oldProvider, newProvider);
+ })
+ .map(AuthenticationProviderView::name)
+ .map(AuthenticationProviderEvent::updated)
+ .forEach(this::notifyListeners);
+ }
+ }
+
+ private void notifyListeners(AuthenticationEvent event) {
+ listeners.forEach(listener -> {
+ try {
+ listener.onEvent(event);
+ } catch (Exception exception) {
+ LOG.error("Couldn't notify listener", exception);
+ }
+ });
+ }
+
+ @Override
+ public void listen(AuthenticationListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void stopListen(AuthenticationListener listener) {
+ listeners.remove(listener);
+ }
+
@TestOnly
public void authEnabled(boolean authEnabled) {
this.authEnabled = authEnabled;
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
new file mode 100644
index 0000000000..8d8673d5e3
--- /dev/null
+++
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java
@@ -0,0 +1,66 @@
+/*
+ * 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.security.authentication;
+
+import
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView;
+import
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Equality verifier for {@link AuthenticationProviderView}.
+ */
+public class AuthenticationProviderEqualityVerifier {
+ /**
+ * Checks if two {@link AuthenticationProviderView} are equal.
+ *
+ * @param o1 First object.
+ * @param o2 Second object.
+ * @return {@code true} if objects are equal, {@code false} otherwise.
+ */
+ public static boolean areEqual(@Nullable AuthenticationProviderView o1,
@Nullable AuthenticationProviderView o2) {
+ if (o1 == o2) {
+ return true;
+ }
+
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+
+ if (o1.getClass() != o2.getClass()) {
+ return false;
+ }
+
+ if (!o1.type().equals(o2.type())) {
+ return false;
+ }
+
+ if (!o1.name().equals(o2.name())) {
+ return false;
+ }
+
+ if (o1 instanceof BasicAuthenticationProviderView) {
+ return areEqual((BasicAuthenticationProviderView) o1,
(BasicAuthenticationProviderView) o2);
+ }
+
+ return false;
+ }
+
+ private static boolean areEqual(BasicAuthenticationProviderView o1,
BasicAuthenticationProviderView o2) {
+ return o1.username().equals(o2.username()) &&
o1.password().equals(o2.password());
+ }
+}
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 52b3ce8d83..441e703cfa 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
@@ -28,7 +28,7 @@ class AuthenticatorFactory {
AuthenticationType type = AuthenticationType.parse(view.type());
if (type == AuthenticationType.BASIC) {
BasicAuthenticationProviderView basicAuthProviderView =
(BasicAuthenticationProviderView) view;
- return new BasicAuthenticator(basicAuthProviderView.username(),
basicAuthProviderView.password());
+ return new BasicAuthenticator(view.name(),
basicAuthProviderView.username(), basicAuthProviderView.password());
} else {
throw new IllegalArgumentException("Unexpected authentication
type: " + type);
}
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 cb8b492cb5..9372867b61 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
@@ -26,11 +26,21 @@ 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 password;
- public BasicAuthenticator(String username, String password) {
+ /**
+ * Constructor.
+ *
+ * @param authenticatorName Authenticator name.
+ * @param username Username.
+ * @param password Password.
+ */
+ public BasicAuthenticator(String authenticatorName, String username,
String password) {
+ this.authenticatorName = authenticatorName;
this.username = username;
this.password = password;
}
@@ -44,7 +54,7 @@ public class BasicAuthenticator implements Authenticator {
}
if (username.equals(authenticationRequest.getIdentity()) &&
password.equals(authenticationRequest.getSecret())) {
- return new UserDetails(username);
+ return new UserDetails(username, authenticatorName);
} else {
throw new InvalidCredentialsException("Invalid credentials");
}
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 f928fb419d..92b1e21c06 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
@@ -17,203 +17,197 @@
package org.apache.ignite.internal.security.authentication;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_DISABLED;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_ENABLED;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_PROVIDER_REMOVED;
+import static
org.apache.ignite.internal.security.authentication.event.EventType.AUTHENTICATION_PROVIDER_UPDATED;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
import
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationEvent;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationListener;
+import
org.apache.ignite.internal.security.authentication.event.AuthenticationProviderEvent;
import org.apache.ignite.internal.security.configuration.SecurityChange;
import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
import org.apache.ignite.internal.security.configuration.SecurityView;
import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
import org.apache.ignite.security.exception.InvalidCredentialsException;
import
org.apache.ignite.security.exception.UnsupportedAuthenticationTypeException;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-
@ExtendWith(ConfigurationExtension.class)
class AuthenticationManagerImplTest extends BaseIgniteAbstractTest {
+ private static final String PROVIDER = "basic";
+
+ private static final String USERNAME = "admin";
+
+ private static final String PASSWORD = "password";
+
+ private static final UsernamePasswordRequest USERNAME_PASSWORD_REQUEST =
new UsernamePasswordRequest(USERNAME, PASSWORD);
+
private final AuthenticationManagerImpl manager = new
AuthenticationManagerImpl();
+ private final List<AuthenticationEvent> events = new ArrayList<>();
+
+ private final AuthenticationListener listener = events::add;
+
@InjectConfiguration
private SecurityConfiguration securityConfiguration;
+ @BeforeEach
+ void setUp() {
+ manager.listen(listener);
+ }
+
@Test
public void enableAuth() {
// when
- SecurityView adminPasswordView = mutateConfiguration(
- securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.create("basic", provider -> {
-
provider.convert(BasicAuthenticationProviderChange.class)
- .changeUsername("admin")
- .changePassword("password");
- }));
- change.changeEnabled(true);
- })
- .value();
-
- manager.onUpdate(new StubSecurityViewEvent(null,
adminPasswordView)).join();
+ enableAuthentication();
// then
// successful authentication with valid credentials
- UsernamePasswordRequest validCredentials = new
UsernamePasswordRequest("admin", "password");
- assertEquals("admin",
manager.authenticate(validCredentials).username());
+ assertEquals(USERNAME,
manager.authenticate(USERNAME_PASSWORD_REQUEST).username());
// and failed authentication with invalid credentials
assertThrows(InvalidCredentialsException.class,
- () -> manager.authenticate(new
UsernamePasswordRequest("admin", "invalid-password")));
+ () -> manager.authenticate(new
UsernamePasswordRequest(USERNAME, "invalid-password")));
+
+ assertEquals(1, events.size());
+ assertEquals(AUTHENTICATION_ENABLED, events.get(0).type());
}
@Test
public void leaveOldSettingWhenInvalidConfiguration() {
// when
+ SecurityView oldValue = securityConfiguration.value();
+
SecurityView invalidAuthView = mutateConfiguration(
securityConfiguration, change -> {
change.changeEnabled(true);
})
.value();
- manager.onUpdate(new StubSecurityViewEvent(null,
invalidAuthView)).join();
+ manager.onUpdate(new StubSecurityViewEvent(oldValue,
invalidAuthView)).join();
// then
// authentication is still disabled
UsernamePasswordRequest emptyCredentials = new
UsernamePasswordRequest("", "");
assertEquals("Unknown",
manager.authenticate(emptyCredentials).username());
+
+ assertEquals(0, events.size());
}
@Test
public void disableAuthEmptyProviders() {
//when
- SecurityView adminPasswordView = mutateConfiguration(
- securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.create("basic", provider -> {
-
provider.convert(BasicAuthenticationProviderChange.class)
- .changeUsername("admin")
- .changePassword("password");
- }));
- change.changeEnabled(true);
- })
- .value();
-
- manager.onUpdate(new StubSecurityViewEvent(null,
adminPasswordView)).join();
+ enableAuthentication();
// then
- // just to be sure that authentication is enabled
- // successful authentication with valid credentials
- UsernamePasswordRequest validCredentials = new
UsernamePasswordRequest("admin", "password");
-
- assertEquals("admin",
manager.authenticate(validCredentials).username());
-
// disable authentication
+ SecurityView currentView = securityConfiguration.value();
+
SecurityView disabledView = mutateConfiguration(
securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.delete("basic"));
+ change.changeAuthentication().changeProviders(providers ->
providers.delete(PROVIDER));
change.changeEnabled(false);
})
.value();
- manager.onUpdate(new StubSecurityViewEvent(adminPasswordView,
disabledView)).join();
+ manager.onUpdate(new StubSecurityViewEvent(currentView,
disabledView)).join();
// then
// authentication is disabled
UsernamePasswordRequest emptyCredentials = new
UsernamePasswordRequest("", "");
assertEquals("Unknown",
manager.authenticate(emptyCredentials).username());
+
+ assertEquals(3, events.size());
+ assertEquals(AUTHENTICATION_ENABLED, events.get(0).type());
+ assertEquals(AUTHENTICATION_DISABLED, events.get(1).type());
+ AuthenticationProviderEvent removed =
assertInstanceOf(AuthenticationProviderEvent.class, events.get(2));
+ assertEquals(AUTHENTICATION_PROVIDER_REMOVED, removed.type());
+ assertEquals(PROVIDER, removed.name());
}
@Test
public void disableAuthNotEmptyProviders() {
//when
- SecurityView adminPasswordView = mutateConfiguration(
- securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.create("basic", provider -> {
-
provider.convert(BasicAuthenticationProviderChange.class)
- .changeUsername("admin")
- .changePassword("password");
- }));
- change.changeEnabled(true);
- })
- .value();
-
- manager.onUpdate(new StubSecurityViewEvent(null,
adminPasswordView)).join();
-
- // then
- // successful authentication with valid credentials
- UsernamePasswordRequest validCredentials = new
UsernamePasswordRequest("admin", "password");
-
- assertEquals("admin",
manager.authenticate(validCredentials).username());
+ enableAuthentication();
// disable authentication
+ SecurityView currentView = securityConfiguration.value();
+
SecurityView disabledView = mutateConfiguration(
securityConfiguration, change -> {
change.changeEnabled(false);
})
.value();
- manager.onUpdate(new StubSecurityViewEvent(adminPasswordView,
disabledView)).join();
+ manager.onUpdate(new StubSecurityViewEvent(currentView,
disabledView)).join();
// then
// authentication is disabled
UsernamePasswordRequest emptyCredentials = new
UsernamePasswordRequest("", "");
assertEquals("Unknown",
manager.authenticate(emptyCredentials).username());
+
+ assertEquals(2, events.size());
+ assertEquals(AUTHENTICATION_ENABLED, events.get(0).type());
+ assertEquals(AUTHENTICATION_DISABLED, events.get(1).type());
}
@Test
public void changedCredentials() {
// when
- SecurityView adminPasswordView = mutateConfiguration(
- securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.create("basic", provider -> {
-
provider.convert(BasicAuthenticationProviderChange.class)
- .changeUsername("admin")
- .changePassword("password");
- }));
- change.changeEnabled(true);
- })
- .value();
-
- manager.onUpdate(new StubSecurityViewEvent(null,
adminPasswordView)).join();
+ enableAuthentication();
// then
- // successful authentication with valid credentials
- UsernamePasswordRequest adminPasswordCredentials = new
UsernamePasswordRequest("admin", "password");
-
- assertEquals("admin",
manager.authenticate(adminPasswordCredentials).username());
-
// change authentication settings - change password
+ SecurityView currentView = securityConfiguration.value();
+
SecurityView adminNewPasswordView = mutateConfiguration(
securityConfiguration, change -> {
- change.changeAuthentication().changeProviders(providers ->
providers.update("basic", provider -> {
+ change.changeAuthentication().changeProviders(providers ->
providers.update(PROVIDER, provider -> {
provider.convert(BasicAuthenticationProviderChange.class)
- .changeUsername("admin")
+ .changeUsername(USERNAME)
.changePassword("new-password");
}));
})
.value();
- manager.onUpdate(new StubSecurityViewEvent(adminPasswordView,
adminNewPasswordView)).join();
+ manager.onUpdate(new StubSecurityViewEvent(currentView,
adminNewPasswordView)).join();
- assertThrows(InvalidCredentialsException.class, () ->
manager.authenticate(adminPasswordCredentials));
+ assertThrows(InvalidCredentialsException.class, () ->
manager.authenticate(USERNAME_PASSWORD_REQUEST));
// then
// successful authentication with the new password
- UsernamePasswordRequest adminNewPasswordCredentials = new
UsernamePasswordRequest("admin", "new-password");
+ UsernamePasswordRequest adminNewPasswordCredentials = new
UsernamePasswordRequest(USERNAME, "new-password");
- assertEquals("admin",
manager.authenticate(adminNewPasswordCredentials).username());
+ assertEquals(USERNAME,
manager.authenticate(adminNewPasswordCredentials).username());
+
+ assertEquals(2, events.size());
+ assertEquals(AUTHENTICATION_ENABLED, events.get(0).type());
+ AuthenticationProviderEvent removed =
assertInstanceOf(AuthenticationProviderEvent.class, events.get(1));
+ assertEquals(AUTHENTICATION_PROVIDER_UPDATED, removed.type());
+ assertEquals(PROVIDER, removed.name());
}
@Test
@@ -230,7 +224,7 @@ class AuthenticationManagerImplTest extends
BaseIgniteAbstractTest {
doThrow(new RuntimeException("Test
exception")).when(authenticator3).authenticate(credentials);
Authenticator authenticator4 = mock(Authenticator.class);
- doReturn(new
UserDetails("admin")).when(authenticator4).authenticate(credentials);
+ doReturn(new UserDetails("admin",
"mock")).when(authenticator4).authenticate(credentials);
manager.authEnabled(true);
manager.authenticators(List.of(authenticator1, authenticator2,
authenticator3, authenticator4));
@@ -243,6 +237,23 @@ class AuthenticationManagerImplTest extends
BaseIgniteAbstractTest {
verify(authenticator4).authenticate(credentials);
}
+ private void enableAuthentication() {
+ SecurityView oldValue = securityConfiguration.value();
+
+ SecurityView adminPasswordView = mutateConfiguration(
+ securityConfiguration, change -> {
+ change.changeAuthentication().changeProviders(providers ->
providers.create(PROVIDER, provider -> {
+
provider.convert(BasicAuthenticationProviderChange.class)
+ .changeUsername(USERNAME)
+ .changePassword(PASSWORD);
+ }));
+ change.changeEnabled(true);
+ })
+ .value();
+
+ manager.onUpdate(new StubSecurityViewEvent(oldValue,
adminPasswordView)).join();
+ }
+
private static SecurityConfiguration
mutateConfiguration(SecurityConfiguration configuration,
Consumer<SecurityChange> consumer) {
CompletableFuture<SecurityConfiguration> future =
configuration.change(consumer)
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 0238d0d5e5..7fe9cce0bd 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
@@ -23,13 +23,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import org.apache.ignite.internal.security.authentication.AnonymousRequest;
import org.apache.ignite.internal.security.authentication.UserDetails;
import
org.apache.ignite.internal.security.authentication.UsernamePasswordRequest;
-import
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticator;
import org.apache.ignite.security.exception.InvalidCredentialsException;
import
org.apache.ignite.security.exception.UnsupportedAuthenticationTypeException;
import org.junit.jupiter.api.Test;
class BasicAuthenticatorTest {
- private final BasicAuthenticator authenticator = new
BasicAuthenticator("admin", "password");
+ private final BasicAuthenticator authenticator = new
BasicAuthenticator("basic", "admin", "password");
@Test
void authenticate() {