Skip to content

Commit 073bd9f

Browse files
authored
RANGER-5367: simplify resource-name parsing (apache#699)
1 parent e16ce56 commit 073bd9f

10 files changed

Lines changed: 647 additions & 515 deletions

File tree

agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServiceDef.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,7 +1417,6 @@ public static class RangerResourceDef implements java.io.Serializable {
14171417
private String rbKeyValidationMessage;
14181418
private Set<String> accessTypeRestrictions;
14191419
private Boolean isValidLeaf;
1420-
private String rrnTemplate; // resource-name template. Examples: {database}.{table}.{column}, {container}@{storageaccount}/{relativepath}
14211420

14221421
public RangerResourceDef() {
14231422
this(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
@@ -1445,7 +1444,6 @@ public RangerResourceDef(RangerResourceDef other) {
14451444
setRbKeyValidationMessage(other.getRbKeyValidationMessage());
14461445
setAccessTypeRestrictions(other.getAccessTypeRestrictions());
14471446
setIsValidLeaf(other.getIsValidLeaf());
1448-
setRrnTemplate(other.getRrnTemplate());
14491447
}
14501448

14511449
public RangerResourceDef(Long itemId, String name, String type, Integer level, String parent, Boolean mandatory, Boolean lookupSupported, Boolean recursiveSupported, Boolean excludesSupported, String matcher, Map<String, String> matcherOptions, String validationRegEx, String validationMessage, String uiHint, String label, String description, String rbKeyLabel, String rbKeyDescription, String rbKeyValidationMessage, Set<String> accessTypeRestrictions, Boolean isValidLeaf) {
@@ -1754,14 +1752,6 @@ public void setIsValidLeaf(Boolean isValidLeaf) {
17541752
this.isValidLeaf = isValidLeaf;
17551753
}
17561754

1757-
public String getRrnTemplate() {
1758-
return rrnTemplate;
1759-
}
1760-
1761-
public void setRrnTemplate(String rrnTemplate) {
1762-
this.rrnTemplate = rrnTemplate;
1763-
}
1764-
17651755
public void dedupStrings(Map<String, String> strTbl) {
17661756
name = StringUtil.dedupString(name, strTbl);
17671757
type = StringUtil.dedupString(type, strTbl);
@@ -1777,7 +1767,6 @@ public void dedupStrings(Map<String, String> strTbl) {
17771767
rbKeyDescription = StringUtil.dedupString(rbKeyDescription, strTbl);
17781768
rbKeyValidationMessage = StringUtil.dedupString(rbKeyValidationMessage, strTbl);
17791769
accessTypeRestrictions = StringUtil.dedupStringsSet(accessTypeRestrictions, strTbl);
1780-
rrnTemplate = StringUtil.dedupString(rrnTemplate, strTbl);
17811770
}
17821771

17831772
public StringBuilder toString(StringBuilder sb) {
@@ -1803,7 +1792,6 @@ public StringBuilder toString(StringBuilder sb) {
18031792
sb.append("rbKeyValidationMessage={").append(rbKeyValidationMessage).append("} ");
18041793
sb.append("accessTypeRestrictions={").append(accessTypeRestrictions == null ? "null" : accessTypeRestrictions.toString()).append("} ");
18051794
sb.append("isValidLeaf={").append(isValidLeaf == null ? "null" : isValidLeaf.toString()).append("} ");
1806-
sb.append("rrnTemplate={").append(rrnTemplate == null ? "null" : rrnTemplate).append("} ");
18071795
sb.append("}");
18081796

18091797
return sb;
@@ -1866,9 +1854,6 @@ public int hashCode() {
18661854
result = prime
18671855
* result
18681856
+ ((isValidLeaf == null) ? 0 : isValidLeaf.hashCode());
1869-
result = prime
1870-
* result
1871-
+ ((rrnTemplate == null) ? 0 : rrnTemplate.hashCode());
18721857
return result;
18731858
}
18741859

@@ -2024,12 +2009,6 @@ public boolean equals(Object obj) {
20242009
return false;
20252010
}
20262011

2027-
if (rrnTemplate == null) {
2028-
return other.rrnTemplate == null;
2029-
} else if (!rrnTemplate.equals(other.rrnTemplate)) {
2030-
return false;
2031-
}
2032-
20332012
return true;
20342013
}
20352014

agents-common/src/main/java/org/apache/ranger/plugin/model/validation/RangerServiceDefHelper.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@
4848
public class RangerServiceDefHelper {
4949
private static final Logger LOG = LoggerFactory.getLogger(RangerServiceDefHelper.class);
5050

51-
public static final String RRN_RESOURCE_PREFIX = "{";
52-
public static final String RRN_RESOURCE_SUFFIX = "}";
53-
public static final String RRN_RESOURCE_SEP = ".";
54-
public static final String RRN_PATH_RESOURCE_SEP = "/";
51+
public static final String RRN_RESOURCE_SEP = "/";
5552

5653
static final Map<String, Delegate> cache = new ConcurrentHashMap<>();
5754
final Delegate delegate;
@@ -497,13 +494,7 @@ public Delegate(RangerServiceDef serviceDef, boolean checkForCycles) {
497494
orderedResourceNames = buildSortedResourceNames();
498495

499496
for (RangerResourceDef resourceDef : serviceDef.getResources()) {
500-
if (StringUtils.isBlank(resourceDef.getRrnTemplate())) {
501-
resourceDef.setRrnTemplate(getDefaultRrnTemplate(resourceDef));
502-
503-
LOG.debug("No rrnTemplate was defined for resource {}.{}. It is now set to default: {}", serviceName, resourceDef.getName(), resourceDef.getRrnTemplate());
504-
}
505-
506-
this.rrnTemplates.put(resourceDef.getName(), resourceDef.getRrnTemplate());
497+
this.rrnTemplates.put(resourceDef.getName(), getDefaultRrnTemplate(resourceDef));
507498
}
508499
} else {
509500
orderedResourceNames = new ArrayList<>();
@@ -901,11 +892,11 @@ public int compareTo(ResourceNameLevel other) {
901892
}
902893

903894
// create default resource-name template for the resource-def, like:
904-
// database:{database}
905-
// table:{database}.{table}
906-
// column:{database}.{table}.{column}
907-
// path:{bucket}/{path}
908-
// key:{volume}.{bucket}/{key}
895+
// database:database
896+
// table:database/table
897+
// column:database/table/column
898+
// path:bucket/path
899+
// key:volume/bucket/key
909900
private String getDefaultRrnTemplate(RangerResourceDef resourceDef) {
910901
List<RangerResourceDef> path = new ArrayList<>();
911902

@@ -919,10 +910,10 @@ private String getDefaultRrnTemplate(RangerResourceDef resourceDef) {
919910
RangerResourceDef res = path.get(i);
920911

921912
if (i > 0) {
922-
sb.append(StringUtils.equalsIgnoreCase(res.getType(), "path") ? RRN_PATH_RESOURCE_SEP : RRN_RESOURCE_SEP);
913+
sb.append(RRN_RESOURCE_SEP);
923914
}
924915

925-
sb.append(RRN_RESOURCE_PREFIX).append(res.getName()).append(RRN_RESOURCE_SUFFIX);
916+
sb.append(res.getName());
926917
}
927918

928919
return sb.toString();

agents-common/src/test/java/org/apache/ranger/plugin/model/validation/TestRangerServiceDefHelper.java

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -382,19 +382,12 @@ public void testRrnTemplateHive() {
382382
RangerServiceDef svcDef = JsonUtils.jsonToObject(reader, RangerServiceDef.class);
383383
RangerServiceDefHelper svcDefHelper = new RangerServiceDefHelper(svcDef);
384384

385-
String rrnDatabase = svcDefHelper.getRrnTemplate("database");
386-
String rrnTable = svcDefHelper.getRrnTemplate("table");
387-
String rrnColumn = svcDefHelper.getRrnTemplate("column");
388-
String rrnUdf = svcDefHelper.getRrnTemplate("udf");
389-
String rrnUrl = svcDefHelper.getRrnTemplate("url");
390-
String rrnUnknown = svcDefHelper.getRrnTemplate("unknown-resource");
391-
392-
assertEquals("{database}", rrnDatabase);
393-
assertEquals("{database}.{table}", rrnTable);
394-
assertEquals("{database}.{table}.{column}", rrnColumn);
395-
assertEquals("{database}.{udf}", rrnUdf);
396-
assertEquals("{url}", rrnUrl);
397-
assertNull(rrnUnknown);
385+
assertEquals("database", svcDefHelper.getRrnTemplate("database"));
386+
assertEquals("database/table", svcDefHelper.getRrnTemplate("table"));
387+
assertEquals("database/table/column", svcDefHelper.getRrnTemplate("column"));
388+
assertEquals("database/udf", svcDefHelper.getRrnTemplate("udf"));
389+
assertEquals("url", svcDefHelper.getRrnTemplate("url"));
390+
assertNull(svcDefHelper.getRrnTemplate("unknown-resource"));
398391
}
399392

400393
@Test
@@ -403,11 +396,8 @@ public void testRrnTemplateS3() {
403396
RangerServiceDef svcDef = JsonUtils.jsonToObject(reader, RangerServiceDef.class);
404397
RangerServiceDefHelper svcDefHelper = new RangerServiceDefHelper(svcDef);
405398

406-
String rrnBucket = svcDefHelper.getRrnTemplate("bucket");
407-
String rrnPath = svcDefHelper.getRrnTemplate("path");
408-
409-
assertEquals("{bucket}", rrnBucket);
410-
assertEquals("{bucket}/{path}", rrnPath);
399+
assertEquals("bucket", svcDefHelper.getRrnTemplate("bucket"));
400+
assertEquals("bucket/path", svcDefHelper.getRrnTemplate("path"));
411401
}
412402

413403
RangerResourceDef createResourceDef(String name, String parent) {

authz-api/src/main/java/org/apache/ranger/authz/api/RangerAuthzApiErrorCode.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ public enum RangerAuthzApiErrorCode implements RangerAuthzErrorCode {
3535
INVALID_REQUEST_PERMISSION_NOT_FOUND(400, "00-011", "{0}: permission not found"),
3636
INVALID_REQUEST_PERMISSIONS_EMPTY(400, "00-012", "permissions is empty. Nothing to authorize"),
3737
INVALID_REQUEST_SERVICE_NAME_OR_TYPE_MANDATORY(400, "00-013", "service name or service type is mandatory"),
38-
INVALID_RESOURCE_TEMPLATE_UNEXPECTED_MARKER_AT(400, "00-014", "invalid resource template: {0}. Unexpected marker \"{1}\" at position {2}"),
38+
INVALID_RESOURCE_TEMPLATE_EMPTY_VALUE(400, "00-014", "invalid resource template - empty"),
3939

4040
INVALID_RESOURCE_TYPE_NOT_VALID(400, "00-015", "invalid resource \"{0}\" - unknown type \"{1}\""),
4141
INVALID_RESOURCE_EMPTY_VALUE(400, "00-016", "invalid resource - empty"),
42-
INVALID_RESOURCE_PREFIX_MISMATCH(400, "00-017", "invalid resource \"{0}\" - prefix \"{1}\" not found"),
43-
INVALID_RESOURCE_SUFFIX_MISMATCH(400, "00-018", "invalid resource \"{0}\" - suffix \"{1}\" not found"),
44-
INVALID_RESOURCE_VALUE(400, "00-019", "invalid resource \"{0}\" - does not match template \"{1}\"");
42+
INVALID_RESOURCE_VALUE(400, "00-017", "invalid resource \"{0}\" - does not match template \"{1}\"");
4543

4644
private static final String ERROR_CODE_MODULE_PREFIX = "AUTHZ";
4745

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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.ranger.authz.util;
21+
22+
import org.apache.commons.lang3.StringUtils;
23+
import org.apache.ranger.authz.api.RangerAuthzException;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import java.util.Collections;
28+
import java.util.HashMap;
29+
import java.util.Map;
30+
import java.util.regex.Pattern;
31+
32+
import static org.apache.ranger.authz.api.RangerAuthzApiErrorCode.INVALID_RESOURCE_EMPTY_VALUE;
33+
import static org.apache.ranger.authz.api.RangerAuthzApiErrorCode.INVALID_RESOURCE_TEMPLATE_EMPTY_VALUE;
34+
import static org.apache.ranger.authz.api.RangerAuthzApiErrorCode.INVALID_RESOURCE_VALUE;
35+
36+
public class RangerResourceNameParser {
37+
private static final Logger LOG = LoggerFactory.getLogger(RangerResourceNameParser.class);
38+
39+
public static final char ESCAPE_CHAR = '\\';
40+
public static final char SEPARATOR_CHAR = '/';
41+
42+
private static final String SEPARATOR_STRING = String.valueOf(SEPARATOR_CHAR);
43+
private static final String ESCAPED_SEPARATOR = "\\\\" + SEPARATOR_STRING;
44+
private static final Pattern SEPARATOR_PATTERN = Pattern.compile(SEPARATOR_STRING);
45+
private static final String[] EMPTY_ARRAY = new String[0];
46+
47+
private final String template; // examples: database/table/column, bucket/volume/path
48+
private final String[] resources; // examples: [database, table, column], [bucket, volume, path]
49+
50+
public RangerResourceNameParser(String template) throws RangerAuthzException {
51+
if (StringUtils.isBlank(template)) {
52+
throw new RangerAuthzException(INVALID_RESOURCE_TEMPLATE_EMPTY_VALUE);
53+
}
54+
55+
this.template = template;
56+
this.resources = template.split(SEPARATOR_STRING); // assumption: '/' is not a valid character in resource names
57+
}
58+
59+
public String getTemplate() {
60+
return template;
61+
}
62+
63+
public String getResourceType() {
64+
return resources[resources.length - 1];
65+
}
66+
67+
public int count() {
68+
return resources.length;
69+
}
70+
71+
public String resourceAt(int index) {
72+
return resources[index];
73+
}
74+
75+
public String[] parseToArray(final String resourceName) throws RangerAuthzException {
76+
if (StringUtils.isBlank(resourceName)) {
77+
throw new RangerAuthzException(INVALID_RESOURCE_EMPTY_VALUE);
78+
}
79+
80+
final String[] ret = new String[resources.length];
81+
final int nameLen = resourceName.length();
82+
final StringBuilder token = new StringBuilder();
83+
int idxToken = 0;
84+
boolean isLastToken = resources.length == 1;
85+
boolean isInEscape = false;
86+
87+
for (int i = 0; i < nameLen; i++) {
88+
char c = resourceName.charAt(i);
89+
90+
if (c == ESCAPE_CHAR) {
91+
if (!isInEscape) {
92+
isInEscape = true;
93+
94+
continue;
95+
}
96+
} else if (c == SEPARATOR_CHAR) {
97+
if (!isInEscape) {
98+
if (!isLastToken) { // for last token, '/' is not a separator
99+
ret[idxToken++] = token.toString();
100+
101+
token.setLength(0);
102+
103+
isLastToken = idxToken == (resources.length - 1);
104+
105+
continue;
106+
}
107+
}
108+
}
109+
110+
token.append(c);
111+
112+
isInEscape = false;
113+
}
114+
115+
ret[idxToken] = token.toString();
116+
117+
if (!isLastToken) {
118+
throw new RangerAuthzException(INVALID_RESOURCE_VALUE, resourceName, template);
119+
}
120+
121+
LOG.debug("parseToArray(resource='{}', template='{}'): ret={}", resourceName, template, ret);
122+
123+
return ret;
124+
}
125+
126+
public Map<String, String> parseToMap(final String resourceName) throws RangerAuthzException {
127+
final String[] arr = parseToArray(resourceName);
128+
final Map<String, String> ret = new HashMap<>(arr.length);
129+
130+
for (int i = 0; i < arr.length; i++) {
131+
ret.put(resources[i], arr[i]);
132+
}
133+
134+
LOG.debug("parseToMap(resourceName='{}', template='{}'): ret={}", resourceName, template, ret);
135+
136+
return ret;
137+
}
138+
139+
public String toResourceName(String[] values) {
140+
StringBuilder ret = new StringBuilder();
141+
142+
if (values == null) {
143+
values = EMPTY_ARRAY;
144+
}
145+
146+
for (int i = 0; i < resources.length; i++) {
147+
String value = values.length > i ? values[i] : null;
148+
boolean isLast = i == (resources.length - 1);
149+
150+
if (value == null) {
151+
value = "";
152+
}
153+
154+
if (!isLast) { // escape '/' in all but the last resource
155+
value = escapeIfNeeded(value);
156+
}
157+
158+
if (i > 0) {
159+
ret.append(SEPARATOR_CHAR);
160+
}
161+
162+
ret.append(value);
163+
}
164+
165+
LOG.debug("toResourceName(values={}, template='{}'): ret='{}'", values, template, ret);
166+
167+
return ret.toString();
168+
}
169+
170+
public String toResourceName(Map<String, String> values) {
171+
StringBuilder ret = new StringBuilder();
172+
173+
if (values == null) {
174+
values = Collections.emptyMap();
175+
}
176+
177+
for (int i = 0; i < resources.length; i++) {
178+
String value = values.get(resources[i]);
179+
boolean isLast = i == (resources.length - 1);
180+
181+
if (value == null) {
182+
value = "";
183+
}
184+
185+
if (!isLast) { // escape '/' in all but the last resource
186+
value = escapeIfNeeded(value);
187+
}
188+
189+
if (i > 0) {
190+
ret.append(SEPARATOR_CHAR);
191+
}
192+
193+
ret.append(value);
194+
}
195+
196+
LOG.debug("toResourceName(values={}, template='{}'): ret='{}'", values, template, ret);
197+
198+
return ret.toString();
199+
}
200+
201+
@Override
202+
public String toString() {
203+
return "RangerResourceTemplate{" +
204+
"template=" + template +
205+
", resources='" + String.join(SEPARATOR_STRING, resources) + "'" +
206+
"}";
207+
}
208+
209+
private String escapeIfNeeded(String value) {
210+
if (value.contains(SEPARATOR_STRING)) {
211+
return SEPARATOR_PATTERN.matcher(value).replaceAll(ESCAPED_SEPARATOR);
212+
} else {
213+
return value;
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)