From ca0f0116488a59b593019469102abbb224c657d4 Mon Sep 17 00:00:00 2001 From: Frantisek Hartman Date: Thu, 31 Aug 2023 15:14:20 +0200 Subject: [PATCH] Check permission before resolving mapping fields [HZ-2991] [5.3.z] `SqlConnector#resolveAndValidateFields` may try to resolve field names from metadata and a sample. This is not covered by any permission check. Added `SqlConnector#permissionsForResolve` that returns permissions required to run the `resolveAndValidateFields`. Propagation of SqlSecurityContext was required for the actuall permission check. EE PR #... (adds security tests) Backport of #25348 --- .../hazelcast/jet/sql/impl/PlanExecutor.java | 5 +- .../hazelcast/jet/sql/impl/SqlPlanImpl.java | 50 +++++++++---------- .../jet/sql/impl/connector/SqlConnector.java | 18 +++++++ .../impl/connector/file/FileSqlConnector.java | 10 ++++ .../sql/impl/schema/TableResolverImpl.java | 29 +++++++---- .../hazelcast/sql/impl/SqlServiceImpl.java | 2 +- .../hazelcast/sql/impl/optimizer/SqlPlan.java | 2 +- .../jet/sql/impl/PlanExecutorTest.java | 4 +- .../sql/impl/cache/PlanCacheTestSupport.java | 2 +- .../impl/schema/TableResolverImplTest.java | 10 ++-- .../jet/impl/connector/ReadFilesP.java | 8 +-- .../impl/function/SecuredFunctions.java | 21 ++++++++ 12 files changed, 112 insertions(+), 49 deletions(-) diff --git a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/PlanExecutor.java b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/PlanExecutor.java index 279492098959..5c3dd0efafed 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/PlanExecutor.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/PlanExecutor.java @@ -93,6 +93,7 @@ import com.hazelcast.sql.impl.schema.type.Type; import com.hazelcast.sql.impl.schema.type.TypeKind; import com.hazelcast.sql.impl.schema.view.View; +import com.hazelcast.sql.impl.security.SqlSecurityContext; import com.hazelcast.sql.impl.state.QueryResultRegistry; import com.hazelcast.sql.impl.type.QueryDataType; import org.apache.calcite.rel.RelNode; @@ -171,8 +172,8 @@ public PlanExecutor( logger = nodeEngine.getLogger(getClass()); } - SqlResult execute(CreateMappingPlan plan) { - catalog.createMapping(plan.mapping(), plan.replace(), plan.ifNotExists()); + SqlResult execute(CreateMappingPlan plan, SqlSecurityContext ssc) { + catalog.createMapping(plan.mapping(), plan.replace(), plan.ifNotExists(), ssc); return UpdateSqlResultImpl.createUpdateCountResult(0); } diff --git a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/SqlPlanImpl.java b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/SqlPlanImpl.java index 4c0f1bcdcc94..6a6ffdf5ecfc 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/SqlPlanImpl.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/SqlPlanImpl.java @@ -148,10 +148,10 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE MAPPING", arguments); SqlPlanImpl.ensureNoTimeout("CREATE MAPPING", timeout); - return planExecutor.execute(this); + return planExecutor.execute(this, ssc); } } @@ -197,7 +197,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("DROP MAPPING", arguments); SqlPlanImpl.ensureNoTimeout("DROP MAPPING", timeout); return planExecutor.execute(this); @@ -278,7 +278,7 @@ public void checkPermissions(SqlSecurityContext context) { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE DATA CONNECTION", arguments); SqlPlanImpl.ensureNoTimeout("CREATE DATA CONNECTION", timeout); return planExecutor.execute(this); @@ -327,7 +327,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoTimeout("DROP DATA CONNECTION", timeout); return planExecutor.execute(this); } @@ -403,7 +403,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE INDEX", arguments); SqlPlanImpl.ensureNoTimeout("CREATE INDEX", timeout); return planExecutor.execute(this); @@ -452,7 +452,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { throw QueryException.error("DROP INDEX is not supported."); } } @@ -530,7 +530,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoTimeout("CREATE JOB", timeout); return planExecutor.execute(this, arguments); } @@ -580,7 +580,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("ALTER JOB", arguments); SqlPlanImpl.ensureNoTimeout("ALTER JOB", timeout); return planExecutor.execute(this); @@ -631,7 +631,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("DROP JOB", arguments); SqlPlanImpl.ensureNoTimeout("DROP JOB", timeout); return planExecutor.execute(this); @@ -675,7 +675,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE SNAPSHOT", arguments); SqlPlanImpl.ensureNoTimeout("CREATE SNAPSHOT", timeout); return planExecutor.execute(this); @@ -719,7 +719,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("DROP SNAPSHOT", arguments); SqlPlanImpl.ensureNoTimeout("DROP SNAPSHOT", timeout); return planExecutor.execute(this); @@ -789,7 +789,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE VIEW", arguments); SqlPlanImpl.ensureNoTimeout("CREATE VIEW", timeout); return planExecutor.execute(this); @@ -838,7 +838,7 @@ public void checkPermissions(SqlSecurityContext context) { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("DROP VIEW", arguments); SqlPlanImpl.ensureNoTimeout("DROP VIEW", timeout); return planExecutor.execute(this); @@ -911,7 +911,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("CREATE TYPE", arguments); SqlPlanImpl.ensureNoTimeout("CREATE TYPE", timeout); return planExecutor.execute(this); @@ -960,7 +960,7 @@ public void checkPermissions(SqlSecurityContext context) { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("DROP TYPE", arguments); SqlPlanImpl.ensureNoTimeout("DROP TYPE", timeout); return planExecutor.execute(this); @@ -1004,7 +1004,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoArguments("SHOW " + showTarget, arguments); SqlPlanImpl.ensureNoTimeout("SHOW " + showTarget, timeout); return planExecutor.execute(this); @@ -1040,7 +1040,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { SqlPlanImpl.ensureNoTimeout("EXPLAIN", timeout); return planExecutor.execute(this); } @@ -1121,7 +1121,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, queryId, arguments, timeout); } } @@ -1201,7 +1201,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, queryId, arguments, timeout); } } @@ -1289,7 +1289,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, queryId, arguments, timeout); } } @@ -1363,7 +1363,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, arguments, timeout); } } @@ -1429,7 +1429,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, arguments, timeout); } } @@ -1511,7 +1511,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, arguments, timeout); } } @@ -1586,7 +1586,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { return planExecutor.execute(this, arguments, timeout); } } diff --git a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/SqlConnector.java b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/SqlConnector.java index 1a11a893e95b..a96e8e8ef025 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/SqlConnector.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/SqlConnector.java @@ -38,12 +38,14 @@ import javax.annotation.Nullable; import java.io.Serializable; import java.lang.reflect.Method; +import java.security.Permission; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; @@ -231,6 +233,22 @@ List resolveAndValidateFields( @Nonnull List userFields ); + /** + * Returns the required permissions to execute + * {@link #resolveAndValidateFields(NodeEngine, SqlExternalResource, List)} method. + *

+ * Implementors of {@link SqlConnector} don't need to override this method when {@code resolveAndValidateFields} + * doesn't support field resolution or when validation doesn't access the external resource. + *

+ * The permissions are usually the same as required permissions to read from the external resource. + * + * @return list of permissions required to run {@link #resolveAndValidateFields} + */ + @Nonnull + default List permissionsForResolve(SqlExternalResource resource, NodeEngine nodeEngine) { + return emptyList(); + } + /** * Creates a {@link Table} object with the given fields. Should return * quickly; specifically it should not attempt to connect to the remote diff --git a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/file/FileSqlConnector.java b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/file/FileSqlConnector.java index 3e7f868bb2f3..beefce20852c 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/file/FileSqlConnector.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/connector/file/FileSqlConnector.java @@ -31,10 +31,14 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.security.Permission; import java.util.List; import java.util.Map; import static com.hazelcast.jet.core.Edge.between; +import static com.hazelcast.security.permission.ActionConstants.ACTION_READ; +import static com.hazelcast.security.permission.ConnectorPermission.file; +import static java.util.Collections.singletonList; public class FileSqlConnector implements SqlConnector { @@ -83,6 +87,12 @@ static List resolveAndValidateFields( return METADATA_RESOLVERS.resolveAndValidateFields(userFields, options); } + @Nonnull + @Override + public List permissionsForResolve(SqlExternalResource resource, NodeEngine nodeEngine) { + return singletonList(file(resource.options().get(OPTION_PATH), ACTION_READ)); + } + @Nonnull @Override public Table createTable( diff --git a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/schema/TableResolverImpl.java b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/schema/TableResolverImpl.java index 3e6b136a5633..5b3b63e6fb10 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/schema/TableResolverImpl.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/jet/sql/impl/schema/TableResolverImpl.java @@ -41,8 +41,10 @@ import com.hazelcast.sql.impl.schema.dataconnection.DataConnectionCatalogEntry; import com.hazelcast.sql.impl.schema.type.Type; import com.hazelcast.sql.impl.schema.view.View; +import com.hazelcast.sql.impl.security.SqlSecurityContext; import javax.annotation.Nonnull; +import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -127,8 +129,8 @@ public void entryRemoved(EntryEvent event) { // region mapping - public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists) { - Mapping resolved = resolveMapping(mapping); + public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists, SqlSecurityContext securityContext) { + Mapping resolved = resolveMapping(mapping, securityContext); String name = resolved.name(); if (ifNotExists) { @@ -141,7 +143,7 @@ public void createMapping(Mapping mapping, boolean replace, boolean ifNotExists) } } - private Mapping resolveMapping(Mapping mapping) { + private Mapping resolveMapping(Mapping mapping, SqlSecurityContext securityContext) { Map options = mapping.options(); String type = mapping.connectorType(); String dataConnection = mapping.dataConnection(); @@ -157,14 +159,23 @@ private Mapping resolveMapping(Mapping mapping) { ? connector.defaultObjectType() : mapping.objectType(); checkNotNull(objectType, "objectType cannot be null"); + + SqlExternalResource externalResource = new SqlExternalResource( + mapping.externalName(), + mapping.dataConnection(), + connector.typeName(), + objectType, + options + ); + + List permissions = connector.permissionsForResolve(externalResource, nodeEngine); + for (Permission permission : permissions) { + securityContext.checkPermission(permission); + } + resolvedFields = connector.resolveAndValidateFields( nodeEngine, - new SqlExternalResource( - mapping.externalName(), - mapping.dataConnection(), - connector.typeName(), - objectType, - options), + externalResource, mapping.fields() ); diff --git a/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/SqlServiceImpl.java b/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/SqlServiceImpl.java index 0a3b9efc89ec..9dbc3b1a342a 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/SqlServiceImpl.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/SqlServiceImpl.java @@ -263,7 +263,7 @@ private SqlResult query0( } // TODO: pageSize ? - return plan.execute(queryId, args0, timeout); + return plan.execute(queryId, args0, timeout, securityContext); } private SqlPlan prepare(String schema, String sql, List arguments, SqlExpectedResultType expectedResultType) { diff --git a/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/optimizer/SqlPlan.java b/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/optimizer/SqlPlan.java index 70093c588d82..a5b7619932fe 100644 --- a/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/optimizer/SqlPlan.java +++ b/hazelcast-sql/src/main/java/com/hazelcast/sql/impl/optimizer/SqlPlan.java @@ -71,5 +71,5 @@ public long getPlanLastUsed() { */ public abstract boolean producesRows(); - public abstract SqlResult execute(QueryId queryId, List arguments, long timeout); + public abstract SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc); } diff --git a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/PlanExecutorTest.java b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/PlanExecutorTest.java index b6f33cca70ee..27b74cbe383b 100644 --- a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/PlanExecutorTest.java +++ b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/PlanExecutorTest.java @@ -102,11 +102,11 @@ public void test_createMappingExecution(boolean replace, boolean ifNotExists) { CreateMappingPlan plan = new CreateMappingPlan(planKey(), mapping, replace, ifNotExists, planExecutor); // when - SqlResult result = planExecutor.execute(plan); + SqlResult result = planExecutor.execute(plan, null); // then assertThat(result.updateCount()).isEqualTo(0); - verify(catalog).createMapping(mapping, replace, ifNotExists); + verify(catalog).createMapping(mapping, replace, ifNotExists, null); } @Test diff --git a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/cache/PlanCacheTestSupport.java b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/cache/PlanCacheTestSupport.java index c96b3906c712..092bb17e664b 100644 --- a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/cache/PlanCacheTestSupport.java +++ b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/cache/PlanCacheTestSupport.java @@ -156,7 +156,7 @@ public boolean producesRows() { } @Override - public SqlResult execute(QueryId queryId, List arguments, long timeout) { + public SqlResult execute(QueryId queryId, List arguments, long timeout, SqlSecurityContext ssc) { throw new UnsupportedOperationException(); } } diff --git a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/schema/TableResolverImplTest.java b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/schema/TableResolverImplTest.java index 92a01d1533e8..cf69655f083e 100644 --- a/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/schema/TableResolverImplTest.java +++ b/hazelcast-sql/src/test/java/com/hazelcast/jet/sql/impl/schema/TableResolverImplTest.java @@ -109,7 +109,7 @@ public void when_createsInvalidMapping_then_throws() { // when // then - assertThatThrownBy(() -> catalog.createMapping(mapping, true, true)) + assertThatThrownBy(() -> catalog.createMapping(mapping, true, true, null)) .hasMessageContaining("expected test exception"); verify(relationsStorage, never()).putIfAbsent(anyString(), (Mapping) any()); verify(relationsStorage, never()).put(anyString(), (Mapping) any()); @@ -133,7 +133,7 @@ public void when_createsDuplicateMapping_then_throws() { // when // then - assertThatThrownBy(() -> catalog.createMapping(mapping, false, false)) + assertThatThrownBy(() -> catalog.createMapping(mapping, false, false, null)) .isInstanceOf(QueryException.class) .hasMessageContaining("Mapping or view already exists: name"); verifyNoInteractions(listener); @@ -155,7 +155,7 @@ public void when_createsDuplicateMappingWithIfNotExists_then_succeeds() { given(relationsStorage.putIfAbsent(eq(mapping.name()), isA(Mapping.class))).willReturn(false); // when - catalog.createMapping(mapping, false, true); + catalog.createMapping(mapping, false, true, null); // then verifyNoInteractions(listener); @@ -176,7 +176,7 @@ public void when_replacesMapping_then_succeeds() { .willReturn(singletonList(new MappingField("field_name", INT))); // when - catalog.createMapping(mapping, true, false); + catalog.createMapping(mapping, true, false, null); // then verify(relationsStorage).put(eq(mapping.name()), isA(Mapping.class)); @@ -204,7 +204,7 @@ public void when_mappingWithNoObjectType_then_usesDefault() { given(connector.defaultObjectType()).willReturn("MyDummyType"); // when - catalog.createMapping(mapping, true, false); + catalog.createMapping(mapping, true, false, null); // then verify(relationsStorage).put(eq(mapping.name()), isA(Mapping.class)); diff --git a/hazelcast/src/main/java/com/hazelcast/jet/impl/connector/ReadFilesP.java b/hazelcast/src/main/java/com/hazelcast/jet/impl/connector/ReadFilesP.java index f60a636aa6e1..6fd770a45f54 100644 --- a/hazelcast/src/main/java/com/hazelcast/jet/impl/connector/ReadFilesP.java +++ b/hazelcast/src/main/java/com/hazelcast/jet/impl/connector/ReadFilesP.java @@ -28,6 +28,7 @@ import com.hazelcast.jet.pipeline.file.impl.FileTraverser; import com.hazelcast.logging.ILogger; import com.hazelcast.logging.Logger; +import com.hazelcast.security.impl.function.SecuredFunctions; import com.hazelcast.security.permission.ConnectorPermission; import javax.annotation.Nonnull; @@ -74,7 +75,7 @@ public final class ReadFilesP extends AbstractProcessor { private LocalFileTraverser traverser; - private ReadFilesP( + public ReadFilesP( @Nonnull String directory, @Nonnull String glob, boolean sharedFileSystem, @@ -174,8 +175,9 @@ private MetaSupplier( @Nonnull @Override public Function get(@Nonnull List
addresses) { - return address -> ProcessorSupplier.of(() -> new ReadFilesP<>(directory, glob, sharedFileSystem, - ignoreFileNotFound, readFileFn)); + return address -> ProcessorSupplier.of(SecuredFunctions.readFilesProcessorFn( + directory, glob, sharedFileSystem, ignoreFileNotFound, readFileFn + )); } @Override diff --git a/hazelcast/src/main/java/com/hazelcast/security/impl/function/SecuredFunctions.java b/hazelcast/src/main/java/com/hazelcast/security/impl/function/SecuredFunctions.java index 2f5fc699b6ec..27f4c900bf45 100644 --- a/hazelcast/src/main/java/com/hazelcast/security/impl/function/SecuredFunctions.java +++ b/hazelcast/src/main/java/com/hazelcast/security/impl/function/SecuredFunctions.java @@ -27,6 +27,7 @@ import com.hazelcast.jet.core.ProcessorSupplier; import com.hazelcast.jet.core.ProcessorSupplier.Context; import com.hazelcast.jet.function.ToResultSetFunction; +import com.hazelcast.jet.impl.connector.ReadFilesP; import com.hazelcast.jet.impl.connector.ReadIListP; import com.hazelcast.jet.impl.connector.ReadJdbcP; import com.hazelcast.jet.impl.connector.StreamFilesP; @@ -220,6 +221,26 @@ public List permissions() { }; } + public static SupplierEx readFilesProcessorFn( + String directory, + String glob, + boolean sharedFileSystem, + boolean ignoreFileNotFound, + FunctionEx> readFileFn) { + + return new SupplierEx<>() { + @Override + public Processor getEx() { + return new ReadFilesP<>(directory, glob, sharedFileSystem, ignoreFileNotFound, readFileFn); + } + + @Override + public List permissions() { + return singletonList(ConnectorPermission.file(directory, ACTION_READ)); + } + }; + } + public static FunctionEx> jsonReadFileFn( String directory, Class type