You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2016/02/10 17:13:01 UTC

[04/24] ambari git commit: AMBARI-14938. Some user-specified auth-to-local rules fail to render when auto generating auth-to-local rules (rlevas)

AMBARI-14938. Some user-specified auth-to-local rules fail to render when auto generating auth-to-local rules (rlevas)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/a396ff02
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/a396ff02
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/a396ff02

Branch: refs/heads/branch-dev-patch-upgrade
Commit: a396ff0203093ba80460657b4f7a4fecfb2b7ed1
Parents: e139ef5
Author: Robert Levas <rl...@hortonworks.com>
Authored: Mon Feb 8 13:39:30 2016 -0500
Committer: Robert Levas <rl...@hortonworks.com>
Committed: Mon Feb 8 13:39:30 2016 -0500

----------------------------------------------------------------------
 .../server/controller/AuthToLocalBuilder.java   | 287 ++++++++++-------
 .../server/controller/KerberosHelperImpl.java   |  15 +-
 .../controller/AuthToLocalBuilderTest.java      | 315 ++++++++++++-------
 3 files changed, 381 insertions(+), 236 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/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 a8fc487..9d6db0a 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
@@ -18,7 +18,10 @@
 
 package org.apache.ambari.server.controller;
 
+import org.apache.commons.lang.StringUtils;
+
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -40,19 +43,18 @@ import java.util.regex.Pattern;
  * <p/>
  * Unqualified Principal (only user is specified):
  * RULE:[1:$1@$0](PRIMARY@REALM)s/.*\/LOCAL_USERNAME/
- * <p>
+ * <p/>
  * Additionally, for each realm included in the rule set, generate a default realm rule
  * in the format: RULE:[1:$1@$0](.*@REALM)s/@.{@literal *}//
- * <p>
+ * <p/>
  * Ordering guarantees for the generated rule string are as follows:
  * <ul>
- *   <li>Rules with the same expected component count are ordered according to match component count</li>
- *   <li>Rules with different expected component count are ordered according to the default string ordering</li>
- *   <li>Rules in the form of .*@REALM are ordered after all other rules with the same expected component count</li>
+ * <li>Rules with the same expected component count are ordered according to match component count</li>
+ * <li>Rules with different expected component count are ordered according to the default string ordering</li>
+ * <li>Rules in the form of .*@REALM are ordered after all other rules with the same expected component count</li>
  * </ul>
- *
  */
-public class AuthToLocalBuilder {
+public class AuthToLocalBuilder implements Cloneable {
   public static final ConcatenationType DEFAULT_CONCATENATION_TYPE = ConcatenationType.NEW_LINES;
 
   /**
@@ -60,43 +62,62 @@ public class AuthToLocalBuilder {
    */
   private Set<Rule> setRules = new TreeSet<Rule>();
 
-
   /**
-   * A flag indicating whether case insensitive support to the local username has been requested. This will append an //L switch to the generic realm rule
+   * The default realm.
    */
-  private boolean caseInsensitiveUser;
+  private final String defaultRealm;
 
   /**
    * A set of additional realm names to reference when generating rules.
    */
-  private Set<String> additionalRealms = new HashSet<String>();
+  private final Set<String> additionalRealms;
+
 
   /**
-   * Default constructor. Case insensitive support false by default
+   * A flag indicating whether case insensitive support to the local username has been requested. This will append an //L switch to the generic realm rule
+   */
+  private boolean caseInsensitiveUser;
+
+  /**
+   * Constructs a new AuthToLocalBuilder.
+   *
+   * @param defaultRealm               a String declaring the default realm
+   * @param additionalRealms           a String containing a comma-delimited list of realm names
+   *                                   to incorporate into the generated rule set
+   * @param caseInsensitiveUserSupport true indicating that case-insensitivity should be enabled;
+   *                                   false otherwise
    */
-  public AuthToLocalBuilder() {
-    this(false, null);
+  public AuthToLocalBuilder(String defaultRealm, String additionalRealms, boolean caseInsensitiveUserSupport) {
+    this(defaultRealm, splitDelimitedString(additionalRealms), caseInsensitiveUserSupport);
   }
 
   /**
    * Constructs a new AuthToLocalBuilder.
    *
+   * @param defaultRealm               a String declaring the default realm
+   * @param additionalRealms           a collection of Strings declaring the set of realm names to
+   *                                   incorporate into the generated rule set
    * @param caseInsensitiveUserSupport true indicating that case-insensitivity should be enabled;
    *                                   false otherwise
-   * @param additionalRealms           a String containing a comma-delimited list of realm names to generate
-   *                                   default auth-to-local rules for
    */
-  public AuthToLocalBuilder(boolean caseInsensitiveUserSupport, String additionalRealms) {
+  public AuthToLocalBuilder(String defaultRealm, Collection<String> additionalRealms, boolean caseInsensitiveUserSupport) {
+    this.defaultRealm = defaultRealm;
+
+    this.additionalRealms = (additionalRealms == null)
+        ? Collections.<String>emptySet()
+        : Collections.unmodifiableSet(new HashSet<String>(additionalRealms));
+
     this.caseInsensitiveUser = caseInsensitiveUserSupport;
+  }
 
-    if ((additionalRealms != null) && !additionalRealms.isEmpty()) {
-      for (String realm : additionalRealms.split("\\s*(?:\\r?\\n|,)\\s*")) {
-        realm = realm.trim();
-        if (!realm.isEmpty()) {
-          this.additionalRealms.add(realm);
-        }
-      }
-    }
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    AuthToLocalBuilder copy = (AuthToLocalBuilder) super.clone();
+
+    /* **** Copy mutable members **** */
+    copy.setRules = new TreeSet<Rule>(setRules);
+
+    return copy;
   }
 
   /**
@@ -106,15 +127,13 @@ public class AuthToLocalBuilder {
    * @param authToLocalRules config property value containing the existing rules
    */
   public void addRules(String authToLocalRules) {
-    if (authToLocalRules != null && ! authToLocalRules.isEmpty()) {
+    if (!StringUtils.isEmpty(authToLocalRules)) {
       String[] rules = authToLocalRules.split("RULE:|DEFAULT");
       for (String r : rules) {
         r = r.trim();
-        if (! r.isEmpty()) {
+        if (!r.isEmpty()) {
           Rule rule = createRule(r);
           setRules.add(rule);
-          // ensure that a default rule is added for each realm
-          addDefaultRealmRule(rule.getPrincipal());
         }
       }
     }
@@ -139,9 +158,7 @@ public class AuthToLocalBuilder {
    * @throws IllegalArgumentException if the provided principal doesn't contain a realm element
    */
   public void addRule(String principal, String localUsername) {
-    if ((principal != null) && (localUsername != null) &&
-        !principal.isEmpty() && !localUsername.isEmpty()) {
-
+    if (!StringUtils.isEmpty(principal) && !StringUtils.isEmpty(localUsername)) {
       Principal p = new Principal(principal);
       if (p.getRealm() == null) {
         throw new IllegalArgumentException(
@@ -158,13 +175,12 @@ public class AuthToLocalBuilder {
    * Generates the auth_to_local rules used by configuration settings such as core-site/auth_to_local.
    * <p/>
    * Each rule is concatenated using the default ConcatenationType, like calling
-   * {@link #generate(String, ConcatenationType)} with {@link #DEFAULT_CONCATENATION_TYPE}
+   * {@link #generate(ConcatenationType)} with {@link #DEFAULT_CONCATENATION_TYPE}
    *
-   * @param realm a string declaring the realm to use in rule set
    * @return a string containing the generated auth-to-local rule set
    */
-  public String generate(String realm) {
-    return generate(realm, null);
+  public String generate() {
+    return generate(null);
   }
 
   /**
@@ -175,14 +191,15 @@ public class AuthToLocalBuilder {
    * If the concatenation type is <code>null</code>, the default concatenation type is assumed -
    * see {@link #DEFAULT_CONCATENATION_TYPE}.
    *
-   * @param realm             a string declaring the realm to use in rule set
    * @param concatenationType the concatenation type to use to generate the rule set string
    * @return a string containing the generated auth-to-local rule set
    */
-  public String generate(String realm, ConcatenationType concatenationType) {
+  public String generate(ConcatenationType concatenationType) {
     StringBuilder builder = new StringBuilder();
     // ensure that a default rule is added for this realm
-    setRules.add(createDefaultRealmRule(realm));
+    if (!StringUtils.isEmpty(defaultRealm)) {
+      setRules.add(createDefaultRealmRule(defaultRealm));
+    }
 
     // ensure that a default realm rule is added for the specified additional realms
     for (String additionalRealm : additionalRealms) {
@@ -233,11 +250,11 @@ public class AuthToLocalBuilder {
    * Add a default realm rule for the realm associated with a principal.
    * If the realm is null or is a wildcard ".*" then no rule id added.
    *
-   * @param principal  principal which contains the realm
+   * @param principal principal which contains the realm
    */
   private void addDefaultRealmRule(Principal principal) {
     String realm = principal.getRealm();
-    if (realm != null && ! realm.equals(".*")) {
+    if (realm != null && !realm.equals(".*")) {
       setRules.add(createDefaultRealmRule(realm));
     }
   }
@@ -245,9 +262,8 @@ public class AuthToLocalBuilder {
   /**
    * Create a rule that expects 2 components in the principal and ignores hostname in the comparison.
    *
-   * @param principal  principal
-   * @param localUser  local user
-   *
+   * @param principal principal
+   * @param localUser local user
    * @return a new rule that ignores hostname in the comparison
    */
   private Rule createHostAgnosticRule(Principal principal, String localUser) {
@@ -262,44 +278,47 @@ public class AuthToLocalBuilder {
   /**
    * Create a default rule for a realm which matches all principals with 1 component and the same realm.
    *
-   * @param realm  realm that the rule is being created for
-   *
-   * @return  a new default realm rule
+   * @param realm realm that the rule is being created for
+   * @return a new default realm rule
    */
   private Rule createDefaultRealmRule(String realm) {
     String caseSensitivityRule = caseInsensitiveUser ? "/L" : "";
 
     return new Rule(new Principal(String.format(".*@%s", realm)),
-      1, 1, String.format("RULE:[1:$1@$0](.*@%s)s/@.*//" + caseSensitivityRule, realm));
+        1, 1, String.format("RULE:[1:$1@$0](.*@%s)s/@.*//" + caseSensitivityRule, realm));
   }
 
   /**
    * Create a rule from an existing string representation.
-   * @param rule  string representation of a rule
    *
-   * @return  a new rule which matches the provided string representation
+   * @param rule string representation of a rule
+   * @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));
   }
 
   /**
-   * Creates and returns a deep copy of this AuthToLocalBuilder.
+   * Given a comma or line delimited list of strings, returns a collection of non-empty strings.
    *
-   * @return a deep copy of this AuthToLocalBuilder
+   * @param string a string to split
+   * @return an array of non-empty strings or null if the source string is empty or null
    */
-  public AuthToLocalBuilder copy() {
-    AuthToLocalBuilder copy = new AuthToLocalBuilder();
+  private static Collection<String> splitDelimitedString(String string) {
+    Collection<String> collection = null;
 
-    // TODO: This needs to be done in a loop rather than use Set.addAll because there may be an issue
-    // TODO: with the Rule.compareTo method?
-    for(Rule rule:setRules) {
-      copy.setRules.add(rule);
+    if (!StringUtils.isEmpty(string)) {
+      collection = new HashSet<String>();
+
+      for (String realm : string.split("\\s*(?:\\r?\\n|,)\\s*")) {
+        realm = realm.trim();
+        if (!realm.isEmpty()) {
+          collection.add(realm);
+        }
+      }
     }
-    copy.caseInsensitiveUser = this.caseInsensitiveUser;
-    copy.additionalRealms.addAll(this.additionalRealms);
 
-    return copy;
+    return collection;
   }
 
 
@@ -311,7 +330,7 @@ public class AuthToLocalBuilder {
      * pattern used to parse existing rules
      */
     private static final Pattern PATTERN_RULE_PARSE =
-      Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*([^\\\\\\n]*)(.|\\n)*");
+        Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*s/(.*?)/(.*?)/([a-zA-Z]*)(?:.|\n)*");
 
     /**
      * associated principal
@@ -336,10 +355,10 @@ public class AuthToLocalBuilder {
     /**
      * 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
+     * @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;
@@ -351,12 +370,12 @@ public class AuthToLocalBuilder {
     /**
      * Constructor.
      *
-     * @param rule  string representation of the rule
+     * @param rule string representation of the rule
      */
     public Rule(String rule) {
       //this.rule = rule;
       Matcher m = PATTERN_RULE_PARSE.matcher(rule);
-      if (! m.matches()) {
+      if (!m.matches()) {
         throw new IllegalArgumentException("Invalid rule: " + rule);
       }
       expectedComponentCount = Integer.valueOf(m.group(1));
@@ -365,18 +384,20 @@ public class AuthToLocalBuilder {
       matchComponentCount = (matchPattern.startsWith("$") ?
           matchPattern.substring(1) :
           matchPattern).
-            split("\\$").length;
+          split("\\$").length;
       String patternRealm = m.group(3);
       principal = new Principal(m.group(4));
-      String replacementRule = m.group(5);
+      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",
+        this.rule = String.format("RULE:[%d:%s@%s](%s)s/%s/%s/%s",
             expectedComponentCount, matchPattern, patternRealm,
-            principal.toString(), replacementRule);
+            principal.toString(), replacementPattern, replacementReplacement, replacementModifier);
       } else {
-        this.rule = String.format("RULE:[%d:%s](%s)%s",
+        this.rule = String.format("RULE:[%d:%s](%s)s/%s/%s/%s",
             expectedComponentCount, matchPattern,
-            principal.toString(), replacementRule);
+            principal.toString(), replacementPattern, replacementReplacement, replacementModifier);
       }
     }
 
@@ -422,55 +443,52 @@ public class AuthToLocalBuilder {
 
     /**
      * Compares rules.
-     * <p>
+     * <p/>
      * For rules with different expected component counts, the default string comparison is used.
      * For rules with the same expected component count rules are ordered so that rules with a higher
      * match component count occur first.
-     * <p>
+     * <p/>
      * For rules with the same expected component count, default realm rules in the form of
      * .*@myRealm.com are ordered last.
      *
-     * @param other  the other rule to compare
-     *
+     * @param other the other rule to compare
      * @return a negative integer, zero, or a positive integer as this object is less than,
-     *         equal to, or greater than the specified object
+     * equal to, or greater than the specified object
      */
     @Override
     public int compareTo(Rule other) {
-      Principal thatPrincipal = other.getPrincipal();
-      //todo: better implementation that recursively evaluates realm and all components
-      if (expectedComponentCount != other.getExpectedComponentCount()) {
-        return rule.compareTo(other.rule);
-      } else {
-        if (matchComponentCount != other.getMatchComponentCount()) {
-          return other.getMatchComponentCount() - matchComponentCount;
-        } else {
-          if (principal.equals(thatPrincipal)) {
-            return rule.compareTo(other.rule);
+      int retVal = expectedComponentCount - other.getExpectedComponentCount();
+
+      if (retVal == 0) {
+        retVal = other.getMatchComponentCount() - matchComponentCount;
+
+        if (retVal == 0) {
+          Principal otherPrincipal = other.getPrincipal();
+          if (principal.equals(otherPrincipal)) {
+            retVal = rule.compareTo(other.rule);
           } else {
             // check for wildcard realms '.*'
             String realm = principal.getRealm();
-            String thatRealm = thatPrincipal.getRealm();
-            if (realm == null ? thatRealm != null : ! realm.equals(thatRealm)) {
-              if (realm != null && realm.equals(".*")) {
-                return 1;
-              } else if (thatRealm != null && thatRealm.equals(".*")) {
-                return -1;
+            String otherRealm = otherPrincipal.getRealm();
+            retVal = compareValueWithWildcards(realm, otherRealm);
+
+            if (retVal == 0) {
+              for (int i = 1; i <= matchComponentCount; i++) {
+                // check for wildcard component
+                String component1 = principal.getComponent(1);
+                String otherComponent1 = otherPrincipal.getComponent(1);
+                retVal = compareValueWithWildcards(component1, otherComponent1);
+
+                if (retVal != 0) {
+                  break;
+                }
               }
             }
-            // check for wildcard component 1
-            String component1 = principal.getComponent(1);
-            String thatComponent1 = thatPrincipal.getComponent(1);
-            if (component1 != null && component1.equals(".*")) {
-              return 1;
-            } else if(thatComponent1 != null && thatComponent1.equals(".*")) {
-              return -1;
-            } else {
-              return rule.compareTo(other.rule);
-            }
           }
         }
       }
+
+      return retVal;
     }
 
     @Override
@@ -482,6 +500,42 @@ public class AuthToLocalBuilder {
     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/>
+     * Rules:
+     * <ul>
+     * <li><code>null</code> is ordered before any other string except for <code>null</code>, which is considered be equal</li>
+     * <li><code>.*</code> is ordered after any other string except for <code>.*</code>, which is considered equal</li>
+     * <li>All other values are order based on the result of {@link String#compareTo(String)}</li>
+     * </ul>
+     *
+     * @param s1 the first string to be compared.
+     * @param s2 the second string to be compared.
+     * @return a negative integer, zero, or a positive integer as the first argument is less than,
+     * equal to, or greater than the second.
+     * @see Comparable#compareTo(Object)
+     */
+    private int compareValueWithWildcards(String s1, String s2) {
+      if (s1 == null) {
+        if (s2 == null) {
+          return 0;
+        } else {
+          return -1;
+        }
+      } else if (s2 == null) {
+        return 1;
+      } else if (s1.equals(s2)) {
+        return 0;
+      } else if (s1.equals(".*")) {
+        return 1;
+      } else if (s2.equals(".*")) {
+        return -1;
+      } else {
+        return s1.compareTo(s2);
+      }
+    }
   }
 
   /**
@@ -512,7 +566,7 @@ public class AuthToLocalBuilder {
     /**
      * Constructor.
      *
-     * @param principal  string representation of the principal
+     * @param principal string representation of the principal
      */
     public Principal(String principal) {
       this.principal = principal;
@@ -547,7 +601,6 @@ public class AuthToLocalBuilder {
      * Uses the range 1-n to match the notation used in the rule.
      *
      * @param position position of the component in the range 1-n
-     *
      * @return the component at the specified location or null
      */
     public String getComponent(int position) {
@@ -574,16 +627,20 @@ public class AuthToLocalBuilder {
 
     @Override
     public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
 
       Principal principal1 = (Principal) o;
 
       return components.equals(principal1.components) &&
-             principal.equals(principal1.principal) &&
-             !(realm != null ?
-                 !realm.equals(principal1.realm) :
-                 principal1.realm != null);
+          principal.equals(principal1.principal) &&
+          !(realm != null ?
+              !realm.equals(principal1.realm) :
+              principal1.realm != null);
 
     }
 
@@ -622,10 +679,10 @@ public class AuthToLocalBuilder {
      * @return a ConcatenationType
      */
     public static ConcatenationType translate(String value) {
-      if(value != null) {
+      if (value != null) {
         value = value.trim();
 
-        if(!value.isEmpty()) {
+        if (!value.isEmpty()) {
           return valueOf(value.toUpperCase());
         }
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
index 556bed8..fe1ba46 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
@@ -664,7 +664,7 @@ public class KerberosHelperImpl implements KerberosHelper {
       String additionalRealms = kerberosDescriptor.getProperty("additional_realms");
 
       // Determine which properties need to be set
-      AuthToLocalBuilder authToLocalBuilder = new AuthToLocalBuilder(caseInsensitiveUser, additionalRealms);
+      AuthToLocalBuilder authToLocalBuilder = new AuthToLocalBuilder(realm, additionalRealms, caseInsensitiveUser);
       addIdentities(authToLocalBuilder, kerberosDescriptor.getIdentities(), null, existingConfigurations);
 
       authToLocalProperties = kerberosDescriptor.getAuthToLocalProperties();
@@ -750,7 +750,14 @@ public class KerberosHelperImpl implements KerberosHelper {
           Matcher m = KerberosDescriptor.AUTH_TO_LOCAL_PROPERTY_SPECIFICATION_PATTERN.matcher(authToLocalProperty);
 
           if (m.matches()) {
-            AuthToLocalBuilder builder = authToLocalBuilder.copy();
+            AuthToLocalBuilder builder;
+            try {
+              builder = (AuthToLocalBuilder) authToLocalBuilder.clone();
+            } catch (CloneNotSupportedException e) {
+              LOG.error("Failed to clone the AuthToLocalBuilder: " + e.getLocalizedMessage(), e);
+              throw new AmbariException("Failed to clone the AuthToLocalBuilder: " + e.getLocalizedMessage(), e);
+            }
+
             String configType = m.group(1);
             String propertyName = m.group(2);
 
@@ -773,8 +780,8 @@ public class KerberosHelperImpl implements KerberosHelper {
               kerberosConfigurations.put(configType, kerberosConfiguration);
             }
 
-            kerberosConfiguration.put(propertyName, builder.generate(realm,
-                AuthToLocalBuilder.ConcatenationType.translate(m.group(3))));
+            kerberosConfiguration.put(propertyName,
+                builder.generate(AuthToLocalBuilder.ConcatenationType.translate(m.group(3))));
           }
         }
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/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 122e632..c88acc1 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
@@ -22,6 +22,7 @@ import org.apache.ambari.server.utils.CollectionPresentationUtils;
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -30,7 +31,7 @@ public class AuthToLocalBuilderTest {
 
   @Test
   public void testRuleGeneration() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
 
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     // Duplicate principal for secondary namenode, should be filtered out...
@@ -46,22 +47,22 @@ public class AuthToLocalBuilderTest {
 
     assertEquals(
         "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
-        "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
-        "DEFAULT",
-      builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
 
   @Test
   public void testRuleGeneration_caseInsensitiveSupport() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder(true, null);
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), true);
 
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     // Duplicate principal for secondary namenode, should be filtered out...
@@ -76,30 +77,30 @@ public class AuthToLocalBuilderTest {
     builder.addRule("foobar@EXAMPLE.COM", "hdfs");
 
     assertEquals(
-      "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*///L\n" +
-        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
-        "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
-        "DEFAULT",
-      builder.generate("EXAMPLE.COM"));
+        "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*///L\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
   @Test
   public void testRuleGeneration_ExistingRules() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     // previously generated non-host specific rules
     builder.addRule("foobar@EXAMPLE.COM", "hdfs");
     // doesn't exist in latter generation
     builder.addRule("hm/_HOST@EXAMPLE.COM", "hbase");
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
-    String existingRules = builder.generate("EXAMPLE.COM");
+    String existingRules = builder.generate();
 
-    builder = new AuthToLocalBuilder();
+    builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     // set previously existing rules
     builder.addRules(existingRules);
 
@@ -115,32 +116,32 @@ public class AuthToLocalBuilderTest {
 
     assertEquals(
         "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
-        "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
   @Test
   public void testRuleGeneration_ExistingRules_existingMoreSpecificRule() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     // previously generated non-host specific rules
     builder.addRule("foobar@EXAMPLE.COM", "hdfs");
     builder.addRule("hm/_HOST@EXAMPLE.COM", "hbase");
     builder.addRule("jn/_HOST@EXAMPLE.COM", "hdfs");
-    String existingRules = builder.generate("EXAMPLE.COM");
+    String existingRules = builder.generate();
     // prepend host specific rule
     existingRules = "RULE:[2:$1/$2@$0](dn/somehost.com@EXAMPLE.COM)s/.*/hdfs/\n" + existingRules;
     // append default realm rule for additional realm
     existingRules += "\nRULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//";
 
-    builder = new AuthToLocalBuilder();
+    builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     // set previously existing rules
     builder.addRules(existingRules);
     // more specific host qualifed rule exists for dn
@@ -158,29 +159,29 @@ public class AuthToLocalBuilderTest {
 
     assertEquals(
         "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//\n" +
-        "RULE:[2:$1/$2@$0](dn/somehost.com@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
-        "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//\n" +
+            "RULE:[2:$1/$2@$0](dn/somehost.com@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
   @Test
   public void testAddNullExistingRule() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules(null);
 
     assertEquals(
         "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM")
+            "DEFAULT",
+        builder.generate()
     );
   }
 
@@ -194,7 +195,7 @@ public class AuthToLocalBuilderTest {
             "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\\\\\\" +
             "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\\/\\";
 
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules(rules);
 
     assertEquals(
@@ -205,80 +206,116 @@ public class AuthToLocalBuilderTest {
             "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
             "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
             "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+        builder.generate());
   }
 
 
   @Test
+  public void testRuleRegexWithComplexReplacements() {
+    String rules =
+        "RULE:[1:$1@$0](foobar@\\QEXAMPLE1.COM\\E$)s/.*@\\QEXAMPLE1.COM\\E$/hdfs/\n" +
+            "RULE:[1:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" +
+            "RULE:[2:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//";
+
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
+    builder.addRules(rules);
+
+    builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
+    builder.addRule("dn/_HOST@EXAMPLE.COM", "hdfs");
+    builder.addRule("jn/_HOST@EXAMPLE.COM", "hdfs");
+    builder.addRule("rm/_HOST@EXAMPLE.COM", "yarn");
+    builder.addRule("jhs/_HOST@EXAMPLE.COM", "mapred");
+    builder.addRule("hm/_HOST@EXAMPLE.COM", "hbase");
+    builder.addRule("rs/_HOST@EXAMPLE.COM", "hbase");
+    builder.addRule("ambari-qa-c1@EXAMPLE.COM", "ambari-qa");
+
+    assertEquals(
+        "RULE:[1:$1@$0](ambari-qa-c1@EXAMPLE.COM)s/.*/ambari-qa/\n" +
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[1:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" +
+            "RULE:[1:$1@$0](foobar@\\QEXAMPLE1.COM\\E$)s/.*@\\QEXAMPLE1.COM\\E$/hdfs/\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" +
+            "DEFAULT",
+        builder.generate());
+  }
+
+  @Test
   public void testRulesWithWhitespace() {
     String rulesWithWhitespace =
         "RULE:   [1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[  1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[2:   $1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0   ](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0]   (jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)   s/.*/hdfs/\n";
+            "RULE:[  1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[2:   $1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0   ](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0]   (jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)   s/.*/hdfs/\n";
 
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules(rulesWithWhitespace);
 
     assertEquals(
         "RULE:[1:$1@$0](foobar@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
-        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "DEFAULT",
+        builder.generate());
 
   }
 
   @Test
   public void testExistingRuleWithNoRealm() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules("RULE:[1:$1](foobar)s/.*/hdfs/");
 
     assertEquals(
         "RULE:[1:$1](foobar)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
   @Test
   public void testExistingRuleWithNoRealm2() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules("RULE:[1:$1/$2](foobar/someHost)s/.*/hdfs/");
 
     assertEquals(
         "RULE:[1:$1/$2](foobar/someHost)s/.*/hdfs/\n" +
-        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
-  @Test(expected=IllegalArgumentException.class)
+  @Test(expected = IllegalArgumentException.class)
   public void testAddNewRuleWithNoRealm() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
 
     builder.addRule("someUser", "hdfs");
   }
 
-  @Test(expected=IllegalArgumentException.class)
+  @Test(expected = IllegalArgumentException.class)
   public void testAddNewRuleWithNoRealm2() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
 
     builder.addRule("someUser/someHost", "hdfs");
   }
 
   @Test
   public void testExistingWildcardRealm() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
     builder.addRules("RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" +
-                     "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" +
-                     "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" +
-                     "DEFAULT");
+        "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" +
+        "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" +
+        "DEFAULT");
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     builder.addRule("jn/_HOST@EXAMPLE.COM", "hdfs");
 
@@ -287,18 +324,18 @@ public class AuthToLocalBuilderTest {
     // other rules with the same number of expected principal components
     assertEquals(
         "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
-        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" +
-        "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" +
-        "RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" +
-        "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+            "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" +
+            "DEFAULT",
+        builder.generate());
   }
 
   @Test
-  public void testCopy() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder();
+  public void testClone() throws CloneNotSupportedException {
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
 
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     builder.addRule("dn/_HOST@EXAMPLE.COM", "hdfs");
@@ -310,16 +347,18 @@ public class AuthToLocalBuilderTest {
 
     builder.addRule("foobar@EXAMPLE.COM", "hdfs");
 
-    AuthToLocalBuilder copy = builder.copy();
-
+    AuthToLocalBuilder copy = (AuthToLocalBuilder) builder.clone();
     assertNotSame(builder, copy);
-    assertEquals(copy.generate("EXAMPLE.COM"), builder.generate("EXAMPLE.COM"));
+    assertEquals(builder.generate(), copy.generate());
 
+    // Ensure that mutable fields do not change the copy when changed in the original
+    builder.addRule("user@EXAMPLE.COM", "hdfs");
+    assertTrue(!copy.generate().equals(builder.generate()));
   }
 
   @Test
   public void testAdditionalRealms() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder(false, "REALM2,REALM3, REALM1  ");
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "REALM2,REALM3, REALM1  ", false);
 
     builder.addRules(
         "RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//\n" +
@@ -334,26 +373,26 @@ public class AuthToLocalBuilderTest {
     builder.addRule("rs/_HOST@EXAMPLE.COM", "hbase");
 
     // Depends on hashing, string representation can be different
-    List<String> rules = Arrays.asList(new String[]{"RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//",
-                                                    "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//",
-                                                    "RULE:[1:$1@$0](.*@REALM2)s/@.*//",
-                                                    "RULE:[1:$1@$0](.*@REALM1)s/@.*//",
-                                                    "RULE:[1:$1@$0](.*@REALM3)s/@.*//",
-                                                    "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/",
-                                                    "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/",
-                                                    "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/",
-                                                    "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/",
-                                                    "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/",
-                                                    "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/",
-                                                    "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/",
-                                                    "DEFAULT"});
-    assertTrue(CollectionPresentationUtils.isStringPermutationOfCollection(builder.generate("EXAMPLE.COM"), rules,
-               "\n", 0, 0));
+    List<String> rules = Arrays.asList("RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//",
+        "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//",
+        "RULE:[1:$1@$0](.*@REALM2)s/@.*//",
+        "RULE:[1:$1@$0](.*@REALM1)s/@.*//",
+        "RULE:[1:$1@$0](.*@REALM3)s/@.*//",
+        "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/",
+        "RULE:[2:$1@$0](hm@EXAMPLE.COM)s/.*/hbase/",
+        "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/",
+        "RULE:[2:$1@$0](jn@EXAMPLE.COM)s/.*/hdfs/",
+        "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/",
+        "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/",
+        "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/",
+        "DEFAULT");
+    assertTrue(CollectionPresentationUtils.isStringPermutationOfCollection(builder.generate(), rules,
+        "\n", 0, 0));
   }
 
   @Test
   public void testAdditionalRealms_Null() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder(false, null);
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false);
 
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     builder.addRule("dn/_HOST@EXAMPLE.COM", "hdfs");
@@ -373,12 +412,12 @@ public class AuthToLocalBuilderTest {
             "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
             "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
             "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+        builder.generate());
   }
 
   @Test
   public void testAdditionalRealms_Empty() {
-    AuthToLocalBuilder builder = new AuthToLocalBuilder(false, "");
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "", false);
 
     builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
     builder.addRule("dn/_HOST@EXAMPLE.COM", "hdfs");
@@ -398,6 +437,48 @@ public class AuthToLocalBuilderTest {
             "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
             "RULE:[2:$1@$0](rs@EXAMPLE.COM)s/.*/hbase/\n" +
             "DEFAULT",
-        builder.generate("EXAMPLE.COM"));
+        builder.generate());
+  }
+
+  @Test
+  public void testUseCase() {
+    AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "FOOBAR.COM,HW.HDP,BAZ.NET", false);
+
+    String existingRules =
+        "RULE:[1:$1@$0](.*@BAZ.NET)s/@.*//\n" +
+            "RULE:[1:$1@$0](accumulo-c1@EXAMPLE.COM)s/.*/accumulo/\n" +
+            "RULE:[1:$1@$0](ambari-qa-c1@EXAMPLE.COM)s/.*/ambari-qa/\n" +
+            "RULE:[1:$1@$0](hbase-c1@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[1:$1@$0](hdfs-c1@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[1:$1@$0](spark-c1@EXAMPLE.COM)s/.*/spark/\n" +
+            "RULE:[1:$1@$0](tracer-c1@EXAMPLE.COM)s/.*/accumulo/\n" +
+            "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" +
+            "RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//\n" +
+            "RULE:[1:$1@$0](.*@HW.HDP)s/@.*//\n" +
+            "RULE:[2:$1@$0](accumulo@EXAMPLE.COM)s/.*/accumulo/\n" +
+            "RULE:[2:$1@$0](amshbase@EXAMPLE.COM)s/.*/ams/\n" +
+            "RULE:[2:$1@$0](amszk@EXAMPLE.COM)s/.*/ams/\n" +
+            "RULE:[2:$1@$0](dn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](falcon@EXAMPLE.COM)s/.*/falcon/\n" +
+            "RULE:[2:$1@$0](hbase@EXAMPLE.COM)s/.*/hbase/\n" +
+            "RULE:[2:$1@$0](hive@EXAMPLE.COM)s/.*/hive/\n" +
+            "RULE:[2:$1@$0](jhs@EXAMPLE.COM)s/.*/mapred/\n" +
+            "RULE:[2:$1@$0](nm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](nn@EXAMPLE.COM)s/.*/hdfs/\n" +
+            "RULE:[2:$1@$0](oozie@EXAMPLE.COM)s/.*/oozie/\n" +
+            "RULE:[2:$1@$0](rm@EXAMPLE.COM)s/.*/yarn/\n" +
+            "RULE:[2:$1@$0](yarn@EXAMPLE.COM)s/.*/yarn/\n" +
+            "DEFAULT";
+
+    builder.addRules(existingRules);
+
+    builder.addRule("nn/_HOST@EXAMPLE.COM", "hdfs");
+    builder.addRule("dn/_HOST@EXAMPLE.COM", "hdfs");
+    builder.addRule("rm/_HOST@EXAMPLE.COM", "yarn");
+    builder.addRule("yarn/_HOST@EXAMPLE.COM", "yarn");
+    builder.addRule("kafka/_HOST@EXAMPLE.COM", null);
+    builder.addRule("hdfs-c1@EXAMPLE.COM", "hdfs");
+
+    assertEquals(existingRules, builder.generate());
   }
 }
\ No newline at end of file