This is an automated email from the ASF dual-hosted git repository.

namelchev pushed a commit to branch ignite-2.17
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/ignite-2.17 by this push:
     new fdd19e159f7 IGNITE-23958 Fixed security context propagation for async 
transactional operations (#11732)
fdd19e159f7 is described below

commit fdd19e159f7431b0652f8ef337d8cf5a6f1280b6
Author: Mikhail Petrov <[email protected]>
AuthorDate: Fri Dec 20 15:15:13 2024 +0300

    IGNITE-23958 Fixed security context propagation for async transactional 
operations (#11732)
    
    (cherry picked from commit d547038712706b1246e60f38eca55074aec82943)
---
 .../org/apache/ignite/IgniteSystemProperties.java  |  12 ++
 .../processors/cache/GridCacheAdapter.java         |  28 ++-
 .../platform/client/ClientRequestHandler.java      |  15 +-
 ...curityContextInternalFuturePropagationTest.java | 231 +++++++++++++++++++++
 .../ignite/testsuites/SecurityTestSuite.java       |   2 +
 5 files changed, 276 insertions(+), 12 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java 
b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index ae4263392c8..843e13dbba2 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -132,6 +132,7 @@ import static 
org.apache.ignite.internal.processors.performancestatistics.FilePe
 import static 
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_CACHED_STRINGS_THRESHOLD;
 import static 
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FILE_MAX_SIZE;
 import static 
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FLUSH_SIZE;
+import static 
org.apache.ignite.internal.processors.platform.client.ClientRequestHandler.DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS;
 import static 
org.apache.ignite.internal.processors.query.QueryUtils.DFLT_INDEXING_DISCOVERY_HISTORY_SIZE;
 import static 
org.apache.ignite.internal.processors.query.schema.SchemaIndexCachePartitionWorker.DFLT_IGNITE_INDEX_REBUILD_BATCH_SIZE;
 import static 
org.apache.ignite.internal.processors.rest.GridRestProcessor.DFLT_SES_TIMEOUT;
@@ -1319,6 +1320,17 @@ public final class IgniteSystemProperties {
         defaults = "" + DFLT_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT)
     public static final String IGNITE_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT = 
"IGNITE_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT";
 
+    /**
+     *  Timeout in milliseconds that determines how long Ignite will 
synchronously wait for asynchronous thin client
+     *  requests to complete before releasing the thread.
+     */
+    @SystemProperty(
+        value = "Timeout in milliseconds that determines how long Ignite will 
synchronously wait for" +
+            " asynchronous thin client requests to complete before releasing 
the thread",
+        type = Long.class,
+        defaults = "" + DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS)
+    public static final String IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT 
= "IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT";
+
     /**
      * Default value is {@code false}.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
index c2ccd426c05..6843d1f3b08 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
@@ -110,6 +110,8 @@ import 
org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl;
 import 
org.apache.ignite.internal.processors.dr.IgniteDrDataStreamerCacheUpdater;
 import 
org.apache.ignite.internal.processors.performancestatistics.OperationType;
 import 
org.apache.ignite.internal.processors.platform.cache.PlatformCacheEntryFilter;
+import org.apache.ignite.internal.processors.security.OperationSecurityContext;
+import org.apache.ignite.internal.processors.security.SecurityContext;
 import org.apache.ignite.internal.processors.task.GridInternal;
 import 
org.apache.ignite.internal.transactions.IgniteTxHeuristicCheckedException;
 import 
org.apache.ignite.internal.transactions.IgniteTxRollbackCheckedException;
@@ -3850,24 +3852,30 @@ public abstract class GridCacheAdapter<K, V> implements 
IgniteInternalCache<K, V
             };
 
             if (fut != null && !fut.isDone()) {
-                IgniteInternalFuture<T> f = new GridEmbeddedFuture(fut,
-                    (IgniteOutClosure<IgniteInternalFuture>)() -> {
-                        GridFutureAdapter resFut = new GridFutureAdapter();
+                SecurityContext secCtx = 
ctx.kernalContext().security().securityContext();
+
+                IgniteInternalFuture<T> f = new GridEmbeddedFuture<>(
+                    fut,
+                    (IgniteOutClosure<IgniteInternalFuture<T>>)() -> {
+                        GridFutureAdapter<T> resFut = new 
GridFutureAdapter<>();
 
                         
ctx.kernalContext().closure().runLocalSafe((GridPlainRunnable)() -> {
-                            IgniteInternalFuture fut0;
+                            IgniteInternalFuture<T> opFut;
 
                             if (ctx.kernalContext().isStopping())
-                                fut0 = new GridFinishedFuture<>(
+                                opFut = new GridFinishedFuture<>(
                                     new IgniteCheckedException("Operation has 
been cancelled (node or cache is stopping)."));
                             else if (ctx.gate().isStopped())
-                                fut0 = new GridFinishedFuture<>(new 
CacheStoppedException(ctx.name()));
+                                opFut = new GridFinishedFuture<>(new 
CacheStoppedException(ctx.name()));
                             else {
                                 ctx.operationContextPerCall(opCtx);
                                 ctx.shared().txContextReset();
 
-                                try {
-                                    fut0 = op.op(tx0).chain(clo);
+                                try (OperationSecurityContext ignored = 
ctx.kernalContext().security().withContext(secCtx)) {
+                                    opFut = op.op(tx0).chain(clo);
+                                }
+                                catch (Throwable e) {
+                                    opFut = new GridFinishedFuture<>(e);
                                 }
                                 finally {
                                     // It is necessary to clear tx context in 
this thread as well.
@@ -3876,9 +3884,9 @@ public abstract class GridCacheAdapter<K, V> implements 
IgniteInternalCache<K, V
                                 }
                             }
 
-                            fut0.listen(() -> {
+                            opFut.listen(lsnrFut -> {
                                 try {
-                                    resFut.onDone(fut0.get());
+                                    resFut.onDone(lsnrFut.get());
                                 }
                                 catch (Throwable ex) {
                                     resFut.onDone(ex);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
index 56b6636e296..6767011a3e5 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.platform.client;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteIllegalStateException;
 import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.binary.BinaryWriterExImpl;
@@ -39,6 +40,7 @@ import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.plugin.security.SecurityException;
 
+import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT;
 import static 
org.apache.ignite.internal.processors.platform.client.ClientProtocolVersionFeature.BITMAP_FEATURES;
 import static 
org.apache.ignite.internal.processors.platform.client.ClientProtocolVersionFeature.PARTITION_AWARENESS;
 
@@ -47,7 +49,13 @@ import static 
org.apache.ignite.internal.processors.platform.client.ClientProtoc
  */
 public class ClientRequestHandler implements ClientListenerRequestHandler {
     /** Timeout to wait for async requests completion, to handle them as 
regular sync requests. */
-    private static final long ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS = 10L;
+    public static final long DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS = 10L;
+
+    /** */
+    private final long asyncReqWaitTimeout = IgniteSystemProperties.getLong(
+        IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT,
+        DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS
+    );
 
     /** Client context. */
     private final ClientConnectionContext ctx;
@@ -122,10 +130,13 @@ public class ClientRequestHandler implements 
ClientListenerRequestHandler {
         if (req0.isAsync(ctx)) {
             IgniteInternalFuture<ClientResponse> fut = req0.processAsync(ctx);
 
+            if (asyncReqWaitTimeout <= 0)
+                return new ClientAsyncResponse(req0.requestId(), fut);
+
             try {
                 // Give request a chance to be executed and response processed 
by the current thread,
                 // so we can avoid any performance drops caused by async 
requests execution.
-                return fut.get(ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS);
+                return fut.get(asyncReqWaitTimeout);
             }
             catch (IgniteFutureTimeoutCheckedException ignored) {
                 return new ClientAsyncResponse(req0.requestId(), fut);
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
new file mode 100644
index 00000000000..2ab9eb496ca
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.processors.security;
+
+import java.security.Permissions;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import com.google.common.collect.ImmutableSet;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientAuthorizationException;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.IgniteClientFuture;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.TestRecordingCommunicationSpi;
+import 
org.apache.ignite.internal.processors.cache.distributed.GridCacheModuloAffinityFunction;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsSingleMessage;
+import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
+import 
org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.plugin.security.SecurityPermissionSet;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static java.util.Collections.singletonMap;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static 
org.apache.ignite.internal.processors.cache.distributed.GridCacheModuloAffinityFunction.IDX_ATTR;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CLUSTER_STATE;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_PUT;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_READ;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.CACHE_REMOVE;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER;
+import static 
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.NO_PERMISSIONS;
+import static 
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.create;
+
+/** */
+@RunWith(Parameterized.class)
+public class SecurityContextInternalFuturePropagationTest extends 
GridCommonAbstractTest {
+    /** */
+    private static final int PRELOADED_KEY_CNT = 100;
+
+    /** */
+    private static final AtomicInteger KEY_CNTR = new AtomicInteger();
+
+    /** */
+    @Parameterized.Parameter()
+    public Function<ClientCache<Object, Object>, IgniteClientFuture<?>> op;
+
+    /** */
+    @Parameterized.Parameters()
+    public static List<Function<ClientCache<Object, Object>, 
IgniteClientFuture<?>>> data() {
+        return Arrays.asList(
+            cache -> cache.getAllAsync(ImmutableSet.of(nextKey(), nextKey())), 
// 0
+            cache -> cache.getAndPutAsync(nextKey(), 0), // 1
+            cache -> cache.getAndPutIfAbsentAsync(nextKey(), 0), // 2
+            cache -> cache.getAndPutIfAbsentAsync(PRELOADED_KEY_CNT + 
nextKey(), 0), // 3
+            cache -> cache.getAndRemoveAsync(nextKey()), // 4
+            cache -> cache.getAndReplaceAsync(nextKey(), 0), // 5
+            cache -> cache.getAsync(nextKey()), // 6
+            cache -> cache.putAllAsync(new HashMap<>() {{ put(nextKey(), 0); 
put(nextKey(), 0); }}), // 7
+            cache -> cache.putAsync(nextKey(), 0), // 8
+            cache -> cache.putIfAbsentAsync(PRELOADED_KEY_CNT + nextKey(), 0), 
// 9
+            cache -> cache.removeAsync(nextKey()), // 10
+            cache -> {
+                int key = nextKey();
+                return cache.removeAsync(key, key);
+            }, // 11
+            cache -> cache.replaceAsync(nextKey(), 0), // 12
+            cache -> {
+                int key = nextKey();
+
+                return cache.replaceAsync(key, key, 0);
+            } // 13
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setCommunicationSpi(new TestRecordingCommunicationSpi())
+            .setUserAttributes(singletonMap(IDX_ATTR, 
getTestIgniteInstanceIndex(igniteInstanceName)))
+            .setClientConnectorConfiguration(new ClientConnectorConfiguration()
+                .setThreadPoolSize(1))
+            .setPluginProviders(new TestSecurityPluginProvider(
+                igniteInstanceName,
+                "",
+                create()
+                    .defaultAllowAll(false)
+                    .appendSystemPermissions(JOIN_AS_SERVER, 
ADMIN_CLUSTER_STATE)
+                    .appendCachePermissions(DEFAULT_CACHE_NAME, CACHE_CREATE)
+                    .build(),
+                null,
+                false,
+                userData("forbidden_client", NO_PERMISSIONS),
+                userData("allowed_client", create()
+                    .defaultAllowAll(false)
+                    .appendCachePermissions(DEFAULT_CACHE_NAME, CACHE_READ, 
CACHE_PUT, CACHE_REMOVE)
+                    .build())
+            ));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+    }
+
+    /** */
+    @Test
+    public void testSecurityContextInternalFuturePropagation() throws 
Exception {
+        IgniteEx ignite = startGrids(2);
+
+        prepareCache(ignite);
+
+        TestRecordingCommunicationSpi spi = 
TestRecordingCommunicationSpi.spi(grid(1));
+
+        spi.blockMessages(GridDhtPartitionsSingleMessage.class, ignite.name());
+
+        IgniteInternalFuture<IgniteEx> joinFut = GridTestUtils.runAsync(() -> 
startGrid(2));
+
+        spi.waitForBlocked();
+
+        try (
+            IgniteClient allowedCli = startClient("allowed_client");
+            IgniteClient forbiddenCli = startClient("forbidden_client")
+        ) {
+            ClientCache<Object, Object> allowedCliCache = 
allowedCli.cache(DEFAULT_CACHE_NAME);
+            ClientCache<Object, Object> forbiddenCliCache = 
forbiddenCli.cache(DEFAULT_CACHE_NAME);
+
+            IgniteClientFuture<?> op0Fut = op.apply(allowedCliCache);
+
+            // The following operation previously was executed with security 
context associated with the joining node user.
+            IgniteClientFuture<?> op1Fut = op.apply(allowedCliCache);
+
+            // Simulates failure of a chained operation.
+            IgniteClientFuture<?> op2Fut = op.apply(forbiddenCliCache);
+
+            // The failure of one operation in a chain should not leave the 
entire chain in a broken state.
+            IgniteClientFuture<?> op3Fut = op.apply(allowedCliCache);
+
+            spi.stopBlock();
+
+            joinFut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+            op0Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+            op1Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+            try {
+                op2Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+                fail();
+            }
+            catch (Exception e) {
+                assertTrue(X.hasCause(e, "Authorization failed", 
ClientException.class)
+                    || X.hasCause(e, "User is not authorized to perform this 
operation", ClientAuthorizationException.class));
+            }
+
+            op3Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /** */
+    private void prepareCache(IgniteEx ignite) throws Exception {
+        ignite.createCache(new CacheConfiguration<>()
+            .setName(DEFAULT_CACHE_NAME)
+            .setAtomicityMode(TRANSACTIONAL)
+            .setReadFromBackup(false)
+            .setBackups(1)
+            .setAffinity(new GridCacheModuloAffinityFunction(2, 1)));
+
+        awaitPartitionMapExchange();
+
+        try (IgniteClient cli = startClient("allowed_client")) {
+            for (int i = 0; i < PRELOADED_KEY_CNT; i++)
+                cli.cache(DEFAULT_CACHE_NAME).put(i, i);
+        }
+    }
+
+    /** */
+    private static int nextKey() {
+        return KEY_CNTR.incrementAndGet();
+    }
+
+    /** */
+    private static IgniteClient startClient(String login) {
+        return Ignition.startClient(new ClientConfiguration()
+            .setAddresses("127.0.0.1:10800")
+            .setUserName(login)
+            .setUserPassword(""));
+    }
+
+    /** */
+    private static TestSecurityData userData(String login, 
SecurityPermissionSet perms) {
+        return new TestSecurityData(
+            login,
+            "",
+            perms,
+            new Permissions()
+        );
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
index d7cf6a08796..fae179367ec 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
@@ -20,6 +20,7 @@ package org.apache.ignite.testsuites;
 import 
org.apache.ignite.internal.processors.security.IgniteSecurityProcessorTest;
 import org.apache.ignite.internal.processors.security.InvalidServerTest;
 import 
org.apache.ignite.internal.processors.security.NodeSecurityContextPropagationTest;
+import 
org.apache.ignite.internal.processors.security.SecurityContextInternalFuturePropagationTest;
 import 
org.apache.ignite.internal.processors.security.cache.CacheOperationPermissionCheckTest;
 import 
org.apache.ignite.internal.processors.security.cache.CacheOperationPermissionCreateDestroyCheckTest;
 import 
org.apache.ignite.internal.processors.security.cache.ContinuousQueryPermissionCheckTest;
@@ -143,6 +144,7 @@ import org.junit.runners.Suite;
     NodeSecurityContextPropagationTest.class,
     NodeJoinPermissionsTest.class,
     ActivationOnJoinWithoutPermissionsWithPersistenceTest.class,
+    SecurityContextInternalFuturePropagationTest.class,
 })
 public class SecurityTestSuite {
     /** */

Reply via email to