You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by am...@apache.org on 2017/07/24 12:56:56 UTC
ambari git commit: AMBARI-21418. Ambari rebuilds custom auth_to_local
rules changing its case sensitiveness option (/L) depending on the
case_insensitive_username_rules. (amagyar)
Repository: ambari
Updated Branches:
refs/heads/trunk 95e7719b1 -> bfe772be2
AMBARI-21418. Ambari rebuilds custom auth_to_local rules changing its case sensitiveness option (/L) depending on the case_insensitive_username_rules. (amagyar)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/bfe772be
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/bfe772be
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/bfe772be
Branch: refs/heads/trunk
Commit: bfe772be2858dbf488aa940b0e513141fb018dc1
Parents: 95e7719
Author: Attila Magyar <am...@hortonworks.com>
Authored: Mon Jul 24 14:55:50 2017 +0200
Committer: Attila Magyar <am...@hortonworks.com>
Committed: Mon Jul 24 14:55:50 2017 +0200
----------------------------------------------------------------------
.../server/controller/AuthToLocalBuilder.java | 328 +++++++++++--------
.../controller/AuthToLocalBuilderTest.java | 45 +++
2 files changed, 241 insertions(+), 132 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/bfe772be/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java
index 1d4abdd..b301ed2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java
@@ -28,7 +28,11 @@ import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* AuthToLocalBuilder helps to create auth_to_local rules for use in configuration files like
@@ -104,7 +108,7 @@ public class AuthToLocalBuilder implements Cloneable {
this.defaultRealm = defaultRealm;
this.additionalRealms = (additionalRealms == null)
- ? Collections.<String>emptySet()
+ ? Collections.emptySet()
: Collections.unmodifiableSet(new HashSet<>(additionalRealms));
this.caseInsensitiveUser = caseInsensitiveUserSupport;
@@ -126,20 +130,22 @@ public class AuthToLocalBuilder implements Cloneable {
*
* @param authToLocalRules config property value containing the existing rules
*/
- public void addRules(String authToLocalRules) {
+ public AuthToLocalBuilder addRules(String authToLocalRules) {
if (!StringUtils.isEmpty(authToLocalRules)) {
String[] rules = authToLocalRules.split("RULE:|DEFAULT");
for (String r : rules) {
r = r.trim();
if (!r.isEmpty()) {
Rule rule = createRule(r);
- setRules.add(rule);
+ if (!setRules.contains(rule.caseSensitivityInverted())) {
+ setRules.add(rule);
+ }
}
}
}
+ return this;
}
-
/**
* Adds a rule for the given principal and local user.
* The principal must contain a realm component.
@@ -157,7 +163,7 @@ public class AuthToLocalBuilder implements Cloneable {
* @param localUsername a string declaring that local username to map the principal to
* @throws IllegalArgumentException if the provided principal doesn't contain a realm element
*/
- public void addRule(String principal, String localUsername) {
+ public AuthToLocalBuilder addRule(String principal, String localUsername) {
if (!StringUtils.isEmpty(principal) && !StringUtils.isEmpty(localUsername)) {
Principal p = new Principal(principal);
if (p.getRealm() == null) {
@@ -168,6 +174,7 @@ public class AuthToLocalBuilder implements Cloneable {
Rule rule = createHostAgnosticRule(p, localUsername);
setRules.add(rule);
}
+ return this;
}
/**
@@ -264,10 +271,10 @@ public class AuthToLocalBuilder implements Cloneable {
private Rule createHostAgnosticRule(Principal principal, String localUser) {
List<String> principalComponents = principal.getComponents();
int componentCount = principalComponents.size();
-
- return new Rule(principal, componentCount, 1, String.format(
- "RULE:[%d:$1@$0](%s@%s)s/.*/%s/", componentCount,
- principal.getComponent(1), principal.getRealm(), localUser));
+ return new Rule(
+ MatchingRule.ignoreHostWhenComponentCountIs(componentCount),
+ new Principal(principal.getComponent(1) + "@" + principal.getRealm()),
+ new Substitution(".*", localUser, "", false));
}
/**
@@ -278,10 +285,10 @@ public class AuthToLocalBuilder implements Cloneable {
* @return a new default realm rule
*/
private Rule createDefaultRealmRule(String realm, boolean caseInsensitive) {
- String caseSensitivityRule = caseInsensitive ? "/L" : "";
-
- return new Rule(new Principal(String.format(".*@%s", realm)),
- 1, 1, String.format("RULE:[1:$1@$0](.*@%s)s/@.*//" + caseSensitivityRule, realm));
+ return new Rule(
+ MatchingRule.ignoreHostWhenComponentCountIs(1),
+ new Principal(".*@" + realm),
+ new Substitution("@.*", "", "", caseInsensitive));
}
/**
@@ -291,7 +298,7 @@ public class AuthToLocalBuilder implements Cloneable {
* @return a new rule which matches the provided string representation
*/
private Rule createRule(String rule) {
- return new Rule(rule.startsWith("RULE:") ? rule : String.format("RULE:%s", rule));
+ return Rule.parse(rule.startsWith("RULE:") ? rule : String.format("RULE:%s", rule));
}
/**
@@ -317,124 +324,68 @@ public class AuthToLocalBuilder implements Cloneable {
return collection;
}
-
/**
- * Rule implementation.
+ * I represent an auth-to-local rule that maps a principal of the form username/hostname@REALM to username.
*/
private static class Rule implements Comparable<Rule> {
- /**
- * pattern used to parse existing rules
- */
private static final Pattern PATTERN_RULE_PARSE =
- Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*s/(.*?)/(.*?)/([a-zA-Z]*)(?:.|\n)*");
+ Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*s/(.*?)/(.*?)/([a-zA-Z]*)((/L)?)(?:.|\n)*");
+ private final MatchingRule matchingRule;
+ private final Principal principal;
+ private final Substitution substitution;
/**
- * associated principal
+ * @param rule in the following format RULE:[n:string](regexp)s/pattern/replacement/[modifier]/[L]
*/
- private Principal principal;
-
- /**
- * string representation of the rule
- */
- private String rule;
-
- /**
- * expected component count
- */
- private int expectedComponentCount;
-
- /**
- * number of components being matched in the rule
- */
- private int matchComponentCount;
-
- /**
- * Constructor.
- *
- * @param principal principal
- * @param expectedComponentCount number of components needed by a principal to match
- * @param matchComponentCount number of components which are included in the rule evaluation
- * @param rule string representation of the rule
- */
- public Rule(Principal principal, int expectedComponentCount, int matchComponentCount, String rule) {
- this.principal = principal;
- this.expectedComponentCount = expectedComponentCount;
- this.matchComponentCount = matchComponentCount;
- this.rule = rule;
- }
-
- /**
- * Constructor.
- *
- * @param rule string representation of the rule
- */
- public Rule(String rule) {
- //this.rule = rule;
+ public static Rule parse(String rule) {
Matcher m = PATTERN_RULE_PARSE.matcher(rule);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid rule: " + rule);
}
- expectedComponentCount = Integer.valueOf(m.group(1));
-
+ int expectedComponentCount = Integer.valueOf(m.group(1));
String matchPattern = m.group(2);
- matchComponentCount = (matchPattern.startsWith("$") ?
- matchPattern.substring(1) :
- matchPattern).
- split("\\$").length;
- String patternRealm = m.group(3);
- principal = new Principal(m.group(4));
+ String optionalPatternRealm = m.group(3);
+ String matchingRegexp = m.group(4);
String replacementPattern = m.group(5);
String replacementReplacement = m.group(6);
String replacementModifier = m.group(7);
- if (patternRealm != null) {
- this.rule = String.format("RULE:[%d:%s@%s](%s)s/%s/%s/%s",
- expectedComponentCount, matchPattern, patternRealm,
- principal.toString(), replacementPattern, replacementReplacement, replacementModifier);
- } else {
- this.rule = String.format("RULE:[%d:%s](%s)s/%s/%s/%s",
- expectedComponentCount, matchPattern,
- principal.toString(), replacementPattern, replacementReplacement, replacementModifier);
- }
+ String caseSensitivity = m.group(8);
+ return new Rule(
+ new MatchingRule(expectedComponentCount, matchPattern, optionalPatternRealm),
+ new Principal(matchingRegexp),
+ new Substitution(replacementPattern, replacementReplacement, replacementModifier, !caseSensitivity.isEmpty()));
}
- /**
- * Get the associated principal.
- *
- * @return associated principal
- */
- public Principal getPrincipal() {
- return principal;
+ public Rule(MatchingRule matchingRule, Principal principal, Substitution substitution) {
+ this.matchingRule = matchingRule;
+ this.principal = principal;
+ this.substitution = substitution;
}
- /**
- * Get the expected component count. This specified the number of components
- * that a principal must contain to match this rule.
- *
- * @return the expected component count
- */
- public int getExpectedComponentCount() {
- return expectedComponentCount;
+ @Override
+ public String toString() {
+ return String.format("RULE:%s(%s)%s", matchingRule, principal, substitution);
}
- /**
- * Get the match component count. This is the number of components that are evaluated
- * when attempting to match a principal to the rule.
- *
- * @return the match component count
- */
- public int getMatchComponentCount() {
- return matchComponentCount;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Rule rule = (Rule) o;
+ return new EqualsBuilder()
+ .append(matchingRule, rule.matchingRule)
+ .append(principal, rule.principal)
+ .append(substitution, rule.substitution)
+ .isEquals();
}
- /**
- * String representation of the rule in the form
- * RULE:[componentCount:matchString](me@foo.com)s/pattern/localUser/
- *
- * @return string representation of the rule
- */
@Override
- public String toString() {
- return rule;
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(matchingRule)
+ .append(principal)
+ .append(substitution)
+ .toHashCode();
}
/**
@@ -453,26 +404,22 @@ public class AuthToLocalBuilder implements Cloneable {
*/
@Override
public int compareTo(Rule other) {
- int retVal = expectedComponentCount - other.getExpectedComponentCount();
-
+ int retVal = matchingRule.expectedComponentCount - other.matchingRule.expectedComponentCount;
if (retVal == 0) {
- retVal = other.getMatchComponentCount() - matchComponentCount;
-
+ retVal = other.matchingRule.matchComponentCount() - matchingRule.matchComponentCount();
if (retVal == 0) {
- Principal otherPrincipal = other.getPrincipal();
- if (principal.equals(otherPrincipal)) {
- retVal = rule.compareTo(other.rule);
+ if (this.principal.equals(other.principal)) {
+ retVal = toString().compareTo(other.toString());
} else {
// check for wildcard realms '.*'
- String realm = principal.getRealm();
- String otherRealm = otherPrincipal.getRealm();
+ String realm = this.principal.getRealm();
+ String otherRealm = other.principal.getRealm();
retVal = compareValueWithWildcards(realm, otherRealm);
-
if (retVal == 0) {
- for (int i = 1; i <= matchComponentCount; i++) {
+ for (int i = 1; i <= matchingRule.matchComponentCount(); i++) {
// check for wildcard component
- String component1 = principal.getComponent(1);
- String otherComponent1 = otherPrincipal.getComponent(1);
+ String component1 = this.principal.getComponent(1);
+ String otherComponent1 = other.principal.getComponent(1);
retVal = compareValueWithWildcards(component1, otherComponent1);
if (retVal != 0) {
@@ -483,20 +430,9 @@ public class AuthToLocalBuilder implements Cloneable {
}
}
}
-
return retVal;
}
- @Override
- public boolean equals(Object o) {
- return this == o || o instanceof Rule && rule.equals(((Rule) o).rule);
- }
-
- @Override
- public int hashCode() {
- return rule.hashCode();
- }
-
/**
* Compares 2 strings for use in compareTo methods but orders <code>null</code>s first and wildcards last.
* <p/>
@@ -532,6 +468,134 @@ public class AuthToLocalBuilder implements Cloneable {
return s1.compareTo(s2);
}
}
+
+ public Rule caseSensitivityInverted() {
+ return new Rule(matchingRule, principal, substitution.caseSensitivityInverted());
+ }
+ }
+
+ /**
+ * The matching rule part of an auth-to-local rule: [n:string]
+ * Indicates a matching rule where n declares the number of expected components in the principal.
+ * Components are separated by a /, where a user account has one component (ambari-qa) and a service account has two components (nn/fqdn).
+ * The string value declares how to reformat the value to be used in the rest of the expression.
+ * The placeholders are as follows:
+ * $0 - realm
+ * $1 - 1st component
+ * $2 - 2nd component
+ * For example: [2:$1@$0] matches on nn/c6501.ambari.apache.org@EXAMPLE.COM and translates to nn@EXAMPLE.COM
+ */
+ private static class MatchingRule {
+ private final int expectedComponentCount;
+ private final String matchPattern;
+ private final String realmPattern;
+
+ public static MatchingRule ignoreHostWhenComponentCountIs(int expectedComponentCount) {
+ return new MatchingRule(expectedComponentCount, "$1", "$0");
+ }
+
+ public MatchingRule(int expectedComponentCount, String matchPattern, @Nullable String realmPattern) {
+ this.expectedComponentCount = expectedComponentCount;
+ this.matchPattern = matchPattern;
+ this.realmPattern = realmPattern;
+ }
+
+ /**
+ * Get the match component count. This is the number of components that are evaluated
+ * when attempting to match a principal to the rule.
+ */
+ public int matchComponentCount() {
+ return (matchPattern.startsWith("$")
+ ? matchPattern.substring(1)
+ : matchPattern).split("\\$").length;
+ }
+
+ @Override
+ public String toString() {
+ return realmPattern != null
+ ? String.format("[%d:%s@%s]", expectedComponentCount, matchPattern, realmPattern)
+ : String.format("[%d:%s]", expectedComponentCount, matchPattern);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MatchingRule that = (MatchingRule) o;
+ return new EqualsBuilder()
+ .append(expectedComponentCount, that.expectedComponentCount)
+ .append(matchPattern, that.matchPattern)
+ .append(realmPattern, that.realmPattern)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(expectedComponentCount)
+ .append(matchPattern)
+ .append(realmPattern)
+ .toHashCode();
+ }
+ }
+
+ /**
+ * I'm the substitution part of an auth-to-local rule.
+ * I have 4 parts:
+ * s/pattern/replacement/g/L where the last 2 parts are optional.
+ * The pattern part of this expression is a regular expression used to find the portion of the string to replace.
+ * The replacement part of this expression is the value to use for replacing the matched section.
+ * If g is specified after the last /, the replacements will occur for every match in the value, else only the first match is processed.
+ */
+ private static class Substitution {
+ private final String pattern;
+ private final String replacement;
+ private final String modifier;
+ private final boolean caseInsensitiveUser;
+
+ public Substitution(String pattern, String replacement, String modifier, boolean caseInsensitiveUser) {
+ this.pattern = pattern;
+ this.replacement = replacement;
+ this.modifier = modifier;
+ this.caseInsensitiveUser = caseInsensitiveUser;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "s/%s/%s/%s%s",
+ pattern,
+ replacement,
+ modifier,
+ caseInsensitiveUser ? "/L" : "");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Substitution that = (Substitution) o;
+ return new EqualsBuilder()
+ .append(caseInsensitiveUser, that.caseInsensitiveUser)
+ .append(pattern, that.pattern)
+ .append(replacement, that.replacement)
+ .append(modifier, that.modifier)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(pattern)
+ .append(replacement)
+ .append(modifier)
+ .append(caseInsensitiveUser)
+ .toHashCode();
+ }
+
+ public Substitution caseSensitivityInverted() {
+ return new Substitution(pattern, replacement, modifier, !caseInsensitiveUser);
+ }
}
/**
http://git-wip-us.apache.org/repos/asf/ambari/blob/bfe772be/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java
index c08247d..5953231 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java
@@ -563,4 +563,49 @@ public class AuthToLocalBuilderTest {
assertEquals(existingRules, builder.generate());
}
+
+ @Test
+ public void testCustomRuleCanBeAddedWithCaseSensitivity() {
+ AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.emptyList(), false)
+ .addRule("yarn/_HOST@EXAMPLE.COM", "yarn")
+ .addRules(
+ "RULE:[1:$1@$0](.*@HDP01.LOCAL)s/.*/ambari-qa//L\n" +
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+ "DEFAULT");
+ assertEquals(
+ "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+ "RULE:[1:$1@$0](.*@HDP01.LOCAL)s/.*/ambari-qa//L\n" +
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+ "DEFAULT"
+ , builder.generate());
+ }
+
+ @Test
+ public void testCaseSensitivityFlagIsRemovedAfterItWasAddedToAmbariRule() {
+ AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.emptyList(), false)
+ .addRule("yarn/_HOST@EXAMPLE.COM", "yarn")
+ .addRules(
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn//L\n" +
+ "DEFAULT");
+ assertEquals(
+ "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+ "DEFAULT"
+ , builder.generate());
+ }
+
+ @Test
+ public void testCaseSensitivityFlagIsAddedAfterItWasFromAmbariRule() {
+ AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.emptyList(), true)
+ .addRule("yarn/_HOST@EXAMPLE.COM", "yarn")
+ .addRules(
+ "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+ "DEFAULT");
+ assertEquals(
+ "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*///L\n" +
+ "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+ "DEFAULT"
+ , builder.generate());
+ }
}
\ No newline at end of file