Skip to content

Commit 6e2887c

Browse files
committed
Add a parser to handle datatype agnostic changes for time being
1 parent 42930da commit 6e2887c

File tree

5 files changed

+124
-43
lines changed

5 files changed

+124
-43
lines changed

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ public Map<String, String> getConfigOverrides() {
174174

175175
protected static final String VIEW_QUERY = "select * from ns1.layer1_table";
176176

177-
protected static final String VIEW_QUERY_DYNAMIC = "select * from ns1.layer1_table where is_principal_role('ANALYST')";
177+
protected static final String VIEW_QUERY_DYNAMIC =
178+
String.format("SELECT * from ns1.layer1_table where is_principal_role('%s') OR IS_MEMBER()", PRINCIPAL_ROLE1);
178179

179180
public static final Schema SCHEMA =
180181
new Schema(
@@ -363,11 +364,11 @@ public void before(TestInfo testInfo) {
363364
.withQuery("spark", VIEW_QUERY)
364365
.create();
365366
baseCatalog
366-
.buildView(VIEW_NS1A_3_DYNAMIC)
367-
.withSchema(SCHEMA)
368-
.withDefaultNamespace(NS1)
369-
.withQuery("spark", VIEW_QUERY_DYNAMIC)
370-
.create();
367+
.buildView(VIEW_NS1A_3_DYNAMIC)
368+
.withSchema(SCHEMA)
369+
.withDefaultNamespace(NS1)
370+
.withQuery("spark", VIEW_QUERY_DYNAMIC)
371+
.create();
371372
baseCatalog
372373
.buildView(VIEW_NS1B_1)
373374
.withSchema(SCHEMA)

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogHandlerAuthzTest.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,24 +1515,31 @@ public void testLoadViewSufficientPrivileges() {
15151515

15161516
@Test
15171517
public void testLoadViewSufficientPrivilegesWithDynamicViewResolution() {
1518-
// doTestSufficientPrivileges(
1519-
// List.of(
1520-
// PolarisPrivilege.VIEW_READ_PROPERTIES,
1521-
// PolarisPrivilege.VIEW_WRITE_PROPERTIES,
1522-
// PolarisPrivilege.VIEW_FULL_METADATA,
1523-
// PolarisPrivilege.CATALOG_MANAGE_CONTENT),
1524-
// () -> {
1525-
// LoadViewResponse lv1 = newWrapper(Set.of("ANALYST")).loadView(VIEW_NS1A_3_DYNAMIC);
1526-
// Assertions.assertThat(((SQLViewRepresentation) (lv1.metadata().currentVersion().representations().getFirst())).sql()).isEqualTo("select * from ns1.layer1_table where TRUE");
1527-
// },
1528-
// null /* cleanupAction */);
1518+
// doTestSufficientPrivileges(
1519+
// List.of(
1520+
// PolarisPrivilege.VIEW_READ_PROPERTIES,
1521+
// PolarisPrivilege.VIEW_WRITE_PROPERTIES,
1522+
// PolarisPrivilege.VIEW_FULL_METADATA,
1523+
// PolarisPrivilege.CATALOG_MANAGE_CONTENT),
1524+
// () -> {
1525+
// LoadViewResponse lv1 =
1526+
// newWrapper(Set.of("ANALYST")).loadView(VIEW_NS1A_3_DYNAMIC);
1527+
// Assertions.assertThat(((SQLViewRepresentation)
1528+
// (lv1.metadata().currentVersion().representations().getFirst())).sql()).isEqualTo("select *
1529+
// from ns1.layer1_table where TRUE");
1530+
// },
1531+
// null /* cleanupAction */);
15291532
doTestSufficientPrivileges(
1530-
List.of(PolarisPrivilege.VIEW_READ_PROPERTIES),
1531-
() -> {
1532-
LoadViewResponse lv1 = newWrapper().loadView(VIEW_NS1A_3_DYNAMIC);
1533-
Assertions.assertThat(((SQLViewRepresentation) (lv1.metadata().currentVersion().representations().getFirst())).sql()).isEqualTo("select * from ns1.layer1_table where TRUE");
1534-
},
1535-
null /* cleanupAction */);
1533+
List.of(PolarisPrivilege.VIEW_READ_PROPERTIES),
1534+
() -> {
1535+
LoadViewResponse lv1 = newWrapper(Set.of(PRINCIPAL_ROLE1)).loadView(VIEW_NS1A_3_DYNAMIC);
1536+
Assertions.assertThat(
1537+
((SQLViewRepresentation)
1538+
(lv1.metadata().versions().getFirst().representations().getFirst()))
1539+
.sql())
1540+
.isEqualTo("SELECT * FROM ns1.layer1_table WHERE TRUE OR IS_MEMBER()");
1541+
},
1542+
null /* cleanupAction */);
15361543
}
15371544

15381545
@Test

service/common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
implementation("org.apache.iceberg:iceberg-api")
3535
implementation("org.apache.iceberg:iceberg-core")
3636
implementation("org.apache.iceberg:iceberg-aws")
37+
implementation("com.github.jsqlparser:jsqlparser:4.6")
3738

3839
implementation(libs.hadoop.common) {
3940
exclude("org.slf4j", "slf4j-reload4j")

service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/CatalogHandlerUtils.java

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@
2727
import com.google.common.collect.Sets;
2828
import jakarta.enterprise.context.ApplicationScoped;
2929
import jakarta.inject.Inject;
30-
3130
import java.lang.reflect.Field;
32-
import java.lang.reflect.Modifier;
3331
import java.time.OffsetDateTime;
3432
import java.time.ZoneOffset;
3533
import java.util.*;
3634
import java.util.concurrent.atomic.AtomicBoolean;
37-
import java.util.regex.Matcher;
3835
import java.util.regex.Pattern;
3936
import java.util.stream.Collectors;
37+
import net.sf.jsqlparser.expression.Expression;
38+
import net.sf.jsqlparser.expression.Function;
39+
import net.sf.jsqlparser.expression.StringValue;
40+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
41+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
42+
import net.sf.jsqlparser.statement.Statement;
43+
import net.sf.jsqlparser.statement.select.Select;
44+
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
45+
import net.sf.jsqlparser.util.deparser.SelectDeParser;
4046
import org.apache.iceberg.BaseMetadataTable;
4147
import org.apache.iceberg.BaseTable;
4248
import org.apache.iceberg.BaseTransaction;
@@ -91,10 +97,11 @@ public class CatalogHandlerUtils {
9197
private static final String INITIAL_PAGE_TOKEN = "";
9298

9399
// is_principal_role('ANALYST')
94-
private static final Pattern IS_PRINCIPAL_ROLE = Pattern.compile(
100+
private static final Pattern IS_PRINCIPAL_ROLE =
101+
Pattern.compile(
95102
"is_principal_role\\(\\s*'([^']+)'\\s*\\)",
96-
Pattern.CASE_INSENSITIVE // drop this flag if you want case-sensitive
97-
);
103+
Pattern.CASE_INSENSITIVE // drop this flag if you want case-sensitive
104+
);
98105

99106
private final PolarisCallContext polarisCallContext;
100107
private final PolarisConfigurationStore configurationStore;
@@ -521,28 +528,91 @@ public LoadViewResponse createView(
521528
return viewResponse(view, Set.of());
522529
}
523530

531+
// NOTE: This is just put together to do a quick POC
532+
public static String rewriteIdentityFunctions(String sql, Set<String> authenticatedPrincipals) {
533+
Statement statement = null;
534+
try {
535+
statement = CCJSqlParserUtil.parse(sql);
536+
} catch (Exception e) {
537+
return sql;
538+
}
539+
540+
class CustomExpressionDeParser extends ExpressionDeParser {
541+
private boolean isConverted = false;
542+
543+
@Override
544+
public void visit(Function function) {
545+
String name = function.getName().toLowerCase(Locale.ROOT);
546+
if (Set.of("is_principal", "is_principal_role", "is_catalog_role").contains(name)) {
547+
ExpressionList params = function.getParameters();
548+
if (params != null && !params.getExpressions().isEmpty()) {
549+
Expression argExpr = params.getExpressions().getFirst();
550+
String arg =
551+
argExpr instanceof StringValue
552+
? ((StringValue) argExpr).getValue()
553+
: argExpr.toString();
554+
555+
boolean result = evaluateFunction(name, arg);
556+
getBuffer().append(result ? "TRUE" : "FALSE");
557+
isConverted = true;
558+
} else {
559+
super.visit(function);
560+
}
561+
} else {
562+
super.visit(function);
563+
}
564+
}
565+
566+
private boolean evaluateFunction(String functionName, String arg) {
567+
return switch (functionName) {
568+
case "is_principal", "is_catalog_role", "is_principal_role" ->
569+
authenticatedPrincipals.contains(arg);
570+
default -> false;
571+
};
572+
}
573+
574+
public boolean isConverted() {
575+
return isConverted;
576+
}
577+
}
578+
579+
StringBuilder buffer = new StringBuilder();
580+
CustomExpressionDeParser exprDeParser = new CustomExpressionDeParser();
581+
582+
SelectDeParser selectDeParser = new SelectDeParser((ExpressionDeParser) exprDeParser, buffer);
583+
exprDeParser.setSelectVisitor(selectDeParser);
584+
exprDeParser.setBuffer(buffer);
585+
586+
if (statement instanceof Select select) {
587+
select.getSelectBody().accept(selectDeParser);
588+
}
589+
590+
if (exprDeParser.isConverted()) {
591+
return buffer.toString();
592+
} else {
593+
return sql;
594+
}
595+
}
596+
524597
private LoadViewResponse viewResponse(View view, Set<String> authenticatedPrincipals) {
525598
ViewMetadata metadata = asBaseView(view).operations().current();
526-
// in all the representations, find and replace the is_principal_role('ANALYST')
527599
ViewVersion version = metadata.currentVersion();
528600
List<ViewRepresentation> representations = version.representations();
529601
List<ViewRepresentation> identityResolved = new ArrayList<>(representations.size());
530602
for (ViewRepresentation viewRepresentation : representations) {
531603
if (viewRepresentation instanceof SQLViewRepresentation sqlView) {
532-
Matcher m = IS_PRINCIPAL_ROLE.matcher(sqlView.sql());
533-
StringBuffer sb = new StringBuffer(); // efficient incremental builder
534-
while (m.find()) {
535-
String groupName = m.group(1); // captured expected principal name
536-
String replacement = authenticatedPrincipals.contains(groupName) ? "TRUE" : "FALSE";
537-
m.appendReplacement(sb, replacement);
538-
}
539-
m.appendTail(sb);
540-
identityResolved.add(ImmutableSQLViewRepresentation.builder().sql(sb.toString()).dialect(sqlView.dialect()).build());
604+
String modifiedSQL = rewriteIdentityFunctions(sqlView.sql(), authenticatedPrincipals);
605+
identityResolved.add(
606+
ImmutableSQLViewRepresentation.builder()
607+
.sql(modifiedSQL)
608+
.dialect(sqlView.dialect())
609+
.build());
541610
} else {
542611
identityResolved.add(viewRepresentation);
543612
}
544613
}
545-
ImmutableViewVersion resolvedViewVersion = ImmutableViewVersion.builder().from(version).build().withRepresentations(identityResolved);
614+
ImmutableViewVersion resolvedViewVersion =
615+
ImmutableViewVersion.builder().from(version).build().withRepresentations(identityResolved);
546616

547617
// This is a temp hack, for quick POC
548618
Class<?> clazz = metadata.getClass();
@@ -567,7 +637,8 @@ public void viewExists(ViewCatalog catalog, TableIdentifier viewIdentifier) {
567637
}
568638
}
569639

570-
public LoadViewResponse loadView(ViewCatalog catalog, TableIdentifier viewIdentifier, Set<String> principal) {
640+
public LoadViewResponse loadView(
641+
ViewCatalog catalog, TableIdentifier viewIdentifier, Set<String> principal) {
571642
View view = catalog.loadView(viewIdentifier);
572643
return viewResponse(view, principal);
573644
}

service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ public LoadViewResponse loadView(TableIdentifier viewIdentifier) {
10551055
authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
10561056
Set<String> principal = authenticatedPrincipal.getActivatedPrincipalRoleNames();
10571057

1058-
return catalogHandlerUtils.loadView(viewCatalog, viewIdentifier, Set.of("ANALYST"));
1058+
return catalogHandlerUtils.loadView(viewCatalog, viewIdentifier, principal);
10591059
}
10601060

10611061
public LoadViewResponse replaceView(TableIdentifier viewIdentifier, UpdateTableRequest request) {
@@ -1086,7 +1086,8 @@ public void viewExists(TableIdentifier viewIdentifier) {
10861086
authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
10871087

10881088
// TODO: Just skip CatalogHandlers for this one maybe
1089-
catalogHandlerUtils.loadView(viewCatalog, viewIdentifier, authenticatedPrincipal.getActivatedPrincipalRoleNames());
1089+
catalogHandlerUtils.loadView(
1090+
viewCatalog, viewIdentifier, authenticatedPrincipal.getActivatedPrincipalRoleNames());
10901091
}
10911092

10921093
public void renameView(RenameTableRequest request) {

0 commit comments

Comments
 (0)