Skip to content

Commit 32037e5

Browse files
committed
FGAC policies
1 parent bb93edd commit 32037e5

File tree

10 files changed

+405
-5
lines changed

10 files changed

+405
-5
lines changed

polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyTypes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public enum PredefinedPolicyTypes implements PolicyType {
2828
DATA_COMPACTION(0, "system.data-compaction", true),
2929
METADATA_COMPACTION(1, "system.metadata-compaction", true),
3030
ORPHAN_FILE_REMOVAL(2, "system.orphan-file-removal", true),
31-
SNAPSHOT_EXPIRY(3, "system.snapshot-expiry", true);
31+
SNAPSHOT_EXPIRY(3, "system.snapshot-expiry", true),
32+
ACCESS_CONTROL(4, "system.access-control", false);
3233

3334
private final int code;
3435
private final String name;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.polaris.core.policy.content;
21+
22+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
23+
import com.google.common.base.Strings;
24+
import java.util.List;
25+
import java.util.Set;
26+
import org.apache.iceberg.expressions.Expression;
27+
import org.apache.polaris.core.policy.validator.InvalidPolicyException;
28+
29+
public class AccessControlPolicyContent implements PolicyContent {
30+
31+
// Optional, if there means policies is applicable to the role
32+
private String principalRole;
33+
34+
// TODO: model them as iceberg transforms
35+
private List<String> columnProjections;
36+
37+
// Iceberg expressions without context functions for now.
38+
// Use a custom deserializer for the list of Iceberg Expressions
39+
@JsonDeserialize(using = IcebergExpressionListDeserializer.class)
40+
private List<Expression> rowFilters;
41+
42+
private static final String DEFAULT_POLICY_SCHEMA_VERSION = "2025-02-03";
43+
private static final Set<String> POLICY_SCHEMA_VERSIONS = Set.of(DEFAULT_POLICY_SCHEMA_VERSION);
44+
45+
public static AccessControlPolicyContent fromString(String content) {
46+
if (Strings.isNullOrEmpty(content)) {
47+
throw new InvalidPolicyException("Policy is empty");
48+
}
49+
50+
AccessControlPolicyContent policy;
51+
try {
52+
policy = PolicyContentUtil.MAPPER.readValue(content, AccessControlPolicyContent.class);
53+
} catch (Exception e) {
54+
throw new InvalidPolicyException(e);
55+
}
56+
57+
boolean isProjectionsEmpty =
58+
policy.getColumnProjections() == null || policy.getColumnProjections().isEmpty();
59+
boolean isRowFilterEmpty = policy.getRowFilters() == null || policy.getRowFilters().isEmpty();
60+
if (isProjectionsEmpty && isRowFilterEmpty) {
61+
throw new InvalidPolicyException("Policy must contain 'columnProjections' or 'rowFilters'.");
62+
}
63+
64+
return policy;
65+
}
66+
67+
// Constructors, getters, and setters
68+
public AccessControlPolicyContent() {}
69+
70+
public String getPrincipalRole() {
71+
return principalRole;
72+
}
73+
74+
public void setPrincipalRole(String principalRole) {
75+
this.principalRole = principalRole;
76+
}
77+
78+
public List<String> getColumnProjections() {
79+
return columnProjections;
80+
}
81+
82+
public void setAllowedColumns(List<String> columnProjections) {
83+
this.columnProjections = columnProjections;
84+
}
85+
86+
public List<Expression> getRowFilters() {
87+
return rowFilters;
88+
}
89+
90+
public void setRowFilters(List<Expression> rowFilters) {
91+
this.rowFilters = rowFilters;
92+
}
93+
94+
@Override
95+
public String toString() {
96+
return "AccessControlPolicyContent{"
97+
+ "principalRole='"
98+
+ principalRole
99+
+ '\''
100+
+ ", columnProjections="
101+
+ columnProjections
102+
+ ", rowFilters="
103+
+ rowFilters
104+
+ '}';
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.polaris.core.policy.content;
21+
22+
import com.fasterxml.jackson.core.JsonParser;
23+
import com.fasterxml.jackson.databind.DeserializationContext;
24+
import com.fasterxml.jackson.databind.JsonDeserializer;
25+
import com.fasterxml.jackson.databind.JsonNode;
26+
import com.fasterxml.jackson.databind.ObjectMapper;
27+
import java.io.IOException;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import org.apache.iceberg.expressions.Expression;
31+
import org.apache.iceberg.expressions.ExpressionParser;
32+
33+
public class IcebergExpressionListDeserializer extends JsonDeserializer<List<Expression>> {
34+
@Override
35+
public List<Expression> deserialize(JsonParser p, DeserializationContext ctxt)
36+
throws IOException {
37+
ObjectMapper mapper = (ObjectMapper) p.getCodec();
38+
JsonNode node = mapper.readTree(p);
39+
40+
List<Expression> expressions = new ArrayList<>();
41+
if (node.isArray()) {
42+
for (JsonNode element : node) {
43+
// Convert each JSON element back to a string and pass it to ExpressionParser.fromJson
44+
expressions.add(ExpressionParser.fromJson(mapper.writeValueAsString(element)));
45+
}
46+
}
47+
return expressions;
48+
}
49+
}

polaris-core/src/main/java/org/apache/polaris/core/policy/content/PolicyContentUtil.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.fasterxml.jackson.databind.DeserializationFeature;
2222
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import org.apache.iceberg.rest.RESTSerializers;
2324

2425
public class PolicyContentUtil {
2526
public static final ObjectMapper MAPPER = configureMapper();
@@ -30,6 +31,8 @@ private static ObjectMapper configureMapper() {
3031
mapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);
3132
// Fails if a required field is present but explicitly null, e.g., {"enable": null}
3233
mapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true);
34+
// This will make sure all the Iceberg parsers are loaded.
35+
RESTSerializers.registerAll(mapper);
3336
return mapper;
3437
}
3538
}

polaris-core/src/main/java/org/apache/polaris/core/policy/validator/PolicyValidators.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.polaris.core.entity.PolarisEntity;
2323
import org.apache.polaris.core.policy.PolicyEntity;
2424
import org.apache.polaris.core.policy.PredefinedPolicyTypes;
25+
import org.apache.polaris.core.policy.content.AccessControlPolicyContent;
2526
import org.apache.polaris.core.policy.content.maintenance.DataCompactionPolicyContent;
2627
import org.apache.polaris.core.policy.content.maintenance.MetadataCompactionPolicyContent;
2728
import org.apache.polaris.core.policy.content.maintenance.OrphanFileRemovalPolicyContent;
@@ -66,6 +67,9 @@ public static void validate(PolicyEntity policy) {
6667
case ORPHAN_FILE_REMOVAL:
6768
OrphanFileRemovalPolicyContent.fromString(policy.getContent());
6869
break;
70+
case ACCESS_CONTROL:
71+
AccessControlPolicyContent.fromString(policy.getContent());
72+
break;
6973
default:
7074
throw new IllegalArgumentException("Unsupported policy type: " + type.getName());
7175
}

0 commit comments

Comments
 (0)