You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2015/01/22 23:16:43 UTC

[03/14] ambari git commit: AMBARI-9209. Add the ability to append a random value to values in LDAP attributes when generating principals in Active Directory (rlevas)

AMBARI-9209. Add the ability to append a random value to values in LDAP attributes when generating principals in Active Directory (rlevas)


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

Branch: refs/heads/2.0-preview
Commit: 9f291484ae3764975e3f3f2c616288454bca41c5
Parents: ae82067
Author: Robert Levas <rl...@hortonworks.com>
Authored: Wed Jan 21 11:20:30 2015 -0500
Committer: Yusaku Sako <yu...@hortonworks.com>
Committed: Wed Jan 21 12:21:26 2015 -0800

----------------------------------------------------------------------
 .../kerberos/ADKerberosOperationHandler.java    | 161 +++++-----
 .../kerberos/DeconstructedPrincipal.java        | 201 +++++++++++++
 .../kerberos/KerberosOperationHandler.java      |  10 +-
 .../1.10.3-10/configuration/kerberos-env.xml    |  16 +-
 .../ADKerberosOperationHandlerTest.java         | 301 +++++++++++--------
 .../kerberos/DeconstructedPrincipalTest.java    | 113 +++++++
 6 files changed, 582 insertions(+), 220 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
index 20f7e60..b5de64f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
@@ -21,7 +21,7 @@ package org.apache.ambari.server.serveraction.kerberos;
 
 import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
-import org.apache.ambari.server.utils.StageUtils;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.velocity.VelocityContext;
@@ -42,8 +42,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory
@@ -52,15 +50,6 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
 
   private static Log LOG = LogFactory.getLog(ADKerberosOperationHandler.class);
 
-  /**
-   * Regular expression to parse the different principal formats:
-   * primary/instance@REALM
-   * primary@REALM
-   * primary/instance
-   * primary
-   */
-  private static Pattern PATTERN_PRINCIPAL = Pattern.compile("^(([^ /@]+)(?:/([^ /@]+))?)(?:@(.+)?)?$");
-
   private static final String LDAP_CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory";
 
   public final static String KERBEROS_ENV_LDAP_URL = "ldap_url";
@@ -213,27 +202,14 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     if (principal == null) {
       throw new KerberosOperationException("principal is null");
     }
-    NamingEnumeration<SearchResult> searchResultEnum = null;
+
+    DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
     try {
-      searchResultEnum = ldapContext.search(
-          principalContainerDn,
-          "(userPrincipalName=" + principal + ")",
-          searchControls);
-      if (searchResultEnum.hasMore()) {
-        return true;
-      }
+      return (findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal()) != null);
     } catch (NamingException ne) {
       throw new KerberosOperationException("can not check if principal exists: " + principal, ne);
-    } finally {
-      try {
-        if (searchResultEnum != null) {
-          searchResultEnum.close();
-        }
-      } catch (NamingException ne) {
-        // ignore, we can not do anything about it
-      }
     }
-    return false;
   }
 
   /**
@@ -262,31 +238,24 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     }
 
     // TODO: (rlevas) pass components and realm in separately (AMBARI-9122)
-    String realm = null;
-    String principal_primary = null;
-    String principal_instance = null;
-
-    Matcher matcher = PATTERN_PRINCIPAL.matcher(principal);
-    if (matcher.matches()) {
-      principal = matcher.group(1);
-      principal_primary = matcher.group(2);
-      principal_instance = matcher.group(3);
-      realm = matcher.group(4);
-    }
+    DeconstructedPrincipal deconstructedPrincipal = deconstructPrincipal(principal);
 
-    if ((realm == null) || realm.isEmpty()) {
-      realm = getDefaultRealm();
+    String realm = deconstructedPrincipal.getRealm();
+    if (realm == null) {
+      realm = "";
     }
 
     Map<String, Object> context = new HashMap<String, Object>();
-    context.put("principal", principal);
-    context.put("principal_primary", principal_primary);
-    context.put("principal_instance", principal_instance);
+    context.put("normalized_principal", deconstructedPrincipal.getNormalizedPrincipal());
+    context.put("principal_name", deconstructedPrincipal.getPrincipalName());
+    context.put("principal_primary", deconstructedPrincipal.getPrimary());
+    context.put("principal_instance", deconstructedPrincipal.getInstance());
     context.put("realm", realm);
-    context.put("realm_lowercase", (realm == null) ? null : realm.toLowerCase());
+    context.put("realm_lowercase", realm.toLowerCase());
     context.put("password", password);
     context.put("is_service", service);
     context.put("container_dn", this.principalContainerDn);
+    context.put("principal_digest", DigestUtils.sha1Hex(deconstructedPrincipal.getNormalizedPrincipal()));
 
     Map<String, Object> data = processCreateTemplate(context);
 
@@ -300,13 +269,11 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
 
         if ("unicodePwd".equals(key)) {
           if (value instanceof String) {
-            Attribute passwordAttr = new BasicAttribute("unicodePwd");  // password
             try {
-              passwordAttr.add(((String) value).getBytes("UTF-16LE"));
+              attributes.put(new BasicAttribute("unicodePwd", String.format("\"%s\"", password).getBytes("UTF-16LE")));
             } catch (UnsupportedEncodingException ue) {
               throw new KerberosOperationException("Can not encode password with UTF-16LE", ue);
             }
-            attributes.put(passwordAttr);
           }
         } else {
           Attribute attribute = new BasicAttribute(key);
@@ -327,7 +294,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     }
 
     if (cn == null) {
-      cn = String.format("%s@%s", principal, realm);
+      cn = deconstructedPrincipal.getNormalizedPrincipal();
     }
     try {
       Name name = new CompositeName().add(String.format("cn=%s,%s", cn, principalContainerDn));
@@ -359,26 +326,27 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     if (password == null) {
       throw new KerberosOperationException("principal password is null");
     }
+
+    DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
     try {
-      if (!principalExists(principal)) {
-        throw new KerberosOperationException("principal not found : " + principal);
+      String dn = findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal());
+
+      if (dn != null) {
+        ldapContext.modifyAttributes(dn,
+            new ModificationItem[]{
+                new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", String.format("\"%s\"", password).getBytes("UTF-16LE")))
+            }
+        );
+      } else {
+        throw new KerberosOperationException(String.format("Can not set password for principal %s: Not Found", principal));
       }
-    } catch (KerberosOperationException e) {
-      e.printStackTrace();
-    }
-    try {
-      ModificationItem[] mods = new ModificationItem[1];
-      String quotedPasswordVal = "\"" + password + "\"";
-      mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
-          new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE")));
-      ldapContext.modifyAttributes(
-          new CompositeName().add("cn=" + principal + "," + principalContainerDn),
-          mods);
-    } catch (NamingException ne) {
-      throw new KerberosOperationException("Can not set password for principal : " + principal, ne);
-    } catch (UnsupportedEncodingException ue) {
-      throw new KerberosOperationException("Unsupported encoding UTF-16LE", ue);
+    } catch (NamingException e) {
+      throw new KerberosOperationException(String.format("Can not set password for principal %s: %s", principal, e.getMessage()), e);
+    } catch (UnsupportedEncodingException e) {
+      throw new KerberosOperationException("Unsupported encoding UTF-16LE", e);
     }
+
     return 0;
   }
 
@@ -399,18 +367,17 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     if (principal == null) {
       throw new KerberosOperationException("principal is null");
     }
+
+    DeconstructedPrincipal deconstructPrincipal = deconstructPrincipal(principal);
+
     try {
-      if (!principalExists(principal)) {
-        return false;
+      String dn = findPrincipalDN(deconstructPrincipal.getNormalizedPrincipal());
+
+      if (dn != null) {
+        ldapContext.destroySubcontext(dn);
       }
-    } catch (KerberosOperationException e) {
-      e.printStackTrace();
-    }
-    try {
-      Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn);
-      ldapContext.destroySubcontext(name);
-    } catch (NamingException ne) {
-      throw new KerberosOperationException("Can not remove principal: " + principal);
+    } catch (NamingException e) {
+      throw new KerberosOperationException(String.format("Can not remove principal %s: %s", principal, e.getMessage()), e);
     }
 
     return true;
@@ -531,14 +498,14 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     if ((createTemplate == null) || createTemplate.isEmpty()) {
       template = "{" +
           "\"objectClass\": [\"top\", \"person\", \"organizationalPerson\", \"user\"]," +
-          "\"cn\": \"$principal\"," +
+          "\"cn\": \"$principal_name\"," +
           "#if( $is_service )" +
-          "  \"servicePrincipalName\": \"$principal\"," +
+          "  \"servicePrincipalName\": \"$principal_name\"," +
           "#end" +
-          "\"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
-          "\"unicodePwd\": \"\\\"$password\\\"\"," +
+          "\"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+          "\"unicodePwd\": \"$password\"," +
           "\"accountExpires\": \"0\"," +
-          "\"userAccountControl\": \"512\"" +
+          "\"userAccountControl\": \"66048\"" +
           "}";
     } else {
       template = createTemplate;
@@ -566,4 +533,34 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     return data;
   }
 
+  private String findPrincipalDN(String normalizedPrincipal) throws NamingException, KerberosOperationException {
+    String dn = null;
+
+    if (normalizedPrincipal != null) {
+      NamingEnumeration<SearchResult> results = null;
+
+      try {
+        results = ldapContext.search(
+            principalContainerDn,
+            String.format("(userPrincipalName=%s)", normalizedPrincipal),
+            searchControls
+        );
+
+        if ((results != null) && results.hasMore()) {
+          SearchResult result = results.next();
+          dn = result.getNameInNamespace();
+        }
+      } finally {
+        try {
+          if (results != null) {
+            results.close();
+          }
+        } catch (NamingException ne) {
+          // ignore, we can not do anything about it
+        }
+      }
+    }
+
+    return dn;
+  }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
new file mode 100644
index 0000000..f5d8156
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipal.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.serveraction.kerberos;
+
+import javax.annotation.Nullable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * DeconstructedPrincipal manages the different parts of a principal and can be used to get a
+ * normalized principal value
+ * <p/>
+ * A "normalized" principal has the following forms:
+ * <ul>
+ * <li>primary/instance@realm</li>
+ * <li>primary@realm</li>
+ * </ul>
+ * <p/>
+ * This class will create a DeconstructedPrincipal from a String containing a principal using
+ * {@link DeconstructedPrincipal#valueOf(String, String)}
+ */
+class DeconstructedPrincipal {
+  /**
+   * Regular expression to parse the different principal formats:
+   * <ul>
+   * <li>primary/instance@REALM</li>
+   * <li>primary@REALM</li>
+   * <li>primary/instance</li>
+   * <li>primary</li>
+   * </ul>
+   */
+  private static Pattern PATTERN_PRINCIPAL = Pattern.compile("^([^ /@]+)(?:/([^ /@]+))?(?:@(.+)?)?$");
+
+  /**
+   * A String containing the "primary" component of a principal
+   */
+  private final String primary;
+
+  /**
+   * A String containing the "instance" component of a principal
+   */
+  private final String instance;
+
+  /**
+   * A String containing the "realm" component of a principal
+   */
+  private final String realm;
+
+  /**
+   * A String containing the principal name portion of the principal.
+   * The principal name is the combination of the primary and instance components.
+   * This value is generated using the primary, instance, and realm components.
+   */
+  private final String principalName;
+
+  /**
+   * A String containing the complete normalized principal
+   * The normalized principal is the combination of the primary, instance, and realm components.
+   * This value is generated using the primary, instance, and realm components.
+   */
+  private final String normalizedPrincipal;
+
+  /**
+   * Given a principal and a default realm, creates a new DeconstructedPrincipal
+   * <p/>
+   * If the supplied principal does not have a realm component, the default realm (supplied) will be
+   * used.
+   *
+   * @param principal    a String containing the principal to deconstruct
+   * @param defaultRealm a String containing the default realm
+   * @return a new DeconstructedPrincipal
+   */
+  public static DeconstructedPrincipal valueOf(String principal, @Nullable String defaultRealm) {
+    if (principal == null) {
+      throw new IllegalArgumentException("The principal may not be null");
+    }
+
+    Matcher matcher = PATTERN_PRINCIPAL.matcher(principal);
+
+    if (matcher.matches()) {
+      String primary = matcher.group(1);
+      String instance = matcher.group(2);
+      String realm = matcher.group(3);
+
+      if ((realm == null) || realm.isEmpty()) {
+        realm = defaultRealm;
+      }
+
+      return new DeconstructedPrincipal(primary, instance, realm);
+    } else {
+      throw new IllegalArgumentException(String.format("Invalid principal value: %s", principal));
+    }
+  }
+
+
+  /**
+   * Constructs a new DeconstructedPrincipal
+   *
+   * @param primary  a String containing the "primary" component of the principal
+   * @param instance a String containing the "instance" component of the principal
+   * @param realm    a String containing the "realm" component of the principal
+   */
+  protected DeconstructedPrincipal(String primary, String instance, String realm) {
+    this.primary = primary;
+    this.instance = instance;
+    this.realm = realm;
+
+    StringBuilder builder = new StringBuilder();
+
+    if (this.primary != null) {
+      builder.append(primary);
+    }
+
+    if (this.instance != null) {
+      builder.append('/');
+      builder.append(this.instance);
+    }
+
+    this.principalName = builder.toString();
+
+    if (this.realm != null) {
+      builder.append('@');
+      builder.append(this.realm);
+    }
+
+    this.normalizedPrincipal = builder.toString();
+  }
+
+  /**
+   * Gets the primary component of this DeconstructedPrincipal
+   *
+   * @return a String containing the "primary" component of this DeconstructedPrincipal
+   */
+  public String getPrimary() {
+    return primary;
+  }
+
+  /**
+   * Gets the instance component of this DeconstructedPrincipal
+   *
+   * @return a String containing the "instance" component of this DeconstructedPrincipal
+   */
+  public String getInstance() {
+    return instance;
+  }
+
+  /**
+   * Gets the realm component of this DeconstructedPrincipal
+   *
+   * @return a String containing the "realm" component of this DeconstructedPrincipal
+   */
+  public String getRealm() {
+    return realm;
+  }
+
+  /**
+   * Gets the constructed principal name for this DeconstructedPrincipal
+   * <p/>
+   * The principal name is the combination of the primary and instance components:
+   * <ul>
+   * <li>primary/instance</li>
+   * <li>primary</li>
+   * </ul>
+   *
+   * @return a String containing the "realm" component of this DeconstructedPrincipal
+   */
+  public String getPrincipalName() {
+    return principalName;
+  }
+
+  /**
+   * Gets the constructed normalized  principal for this DeconstructedPrincipal
+   * <p/>
+   * The normalized principal is the combination of the primary, instance, and realm components:
+   * <ul>
+   * <li>primary/instance@realm</li>
+   * <li>primary@realm</li>
+   * </ul>
+   *
+   * @return a String containing the "realm" component of this DeconstructedPrincipal
+   */
+  public String getNormalizedPrincipal() {
+    return normalizedPrincipal;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
index 7a9233b..a23aa81 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
@@ -63,7 +63,6 @@ public abstract class KerberosOperationHandler {
   private final static char[] SECURE_PASSWORD_CHARS =
       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?.!$%^*()-_+=~".toCharArray();
 
-
   /**
    * The default set of ciphers to use for creating keytab entries
    */
@@ -432,4 +431,13 @@ public abstract class KerberosOperationHandler {
       }
     }
   }
+
+  protected DeconstructedPrincipal deconstructPrincipal(String principal) throws KerberosOperationException {
+    try {
+      return DeconstructedPrincipal.valueOf(principal, getDefaultRealm());
+    } catch (IllegalArgumentException e) {
+      throw new KerberosOperationException(e.getMessage(), e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
index 85ae018..d37e736 100644
--- a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
+++ b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/configuration/kerberos-env.xml
@@ -40,19 +40,23 @@
   <property require-input="true">
     <name>create_attributes_template</name>
     <description>
-      Customizable JSON document representing the LDAP attributes needed to create a new Kerberos entity in the KDC (Velocity template engine).
+      A Velocity template to use to generate a JSON-formatted document containing the set of
+      attribute names and values needed to create a new Kerberos identity in the relevant KDC.
+      Variables include:
+      principal_name, principal_primary, principal_instance, realm, realm_lowercase,
+      normalized_principal, principal digest, password, is_service, container_dn
     </description>
     <value>
 {
   "objectClass": ["top", "person", "organizationalPerson", "user"],
-  "cn": "$principal",
+  "cn": "$principal_name",
   #if( $is_service )
-  "servicePrincipalName": "$principal",
+  "servicePrincipalName": "$principal_name",
   #end
-  "userPrincipalName": "$principal@$realm.toLowerCase()",
-  "unicodePwd": "\"$password\"",
+  "userPrincipalName": "$normalized_principal.toLowerCase()",
+  "unicodePwd": "$password",
   "accountExpires": "0",
-  "userAccountControl": "512"
+  "userAccountControl": "66048"
 }
     </value>
   </property>

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
index 6a89dbb..8d2a3c4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
@@ -19,6 +19,8 @@
 package org.apache.ambari.server.serveraction.kerberos;
 
 import junit.framework.Assert;
+import org.easymock.Capture;
+import org.easymock.CaptureType;
 import org.easymock.EasyMockSupport;
 import org.easymock.IAnswer;
 import org.junit.Ignore;
@@ -26,21 +28,22 @@ import org.junit.Test;
 
 import javax.naming.AuthenticationException;
 import javax.naming.CommunicationException;
+import javax.naming.Name;
 import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 import javax.naming.ldap.Control;
 import javax.naming.ldap.LdapContext;
 
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.nio.charset.Charset;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.*;
 
 public class ADKerberosOperationHandlerTest extends EasyMockSupport {
   private static final String DEFAULT_ADMIN_PRINCIPAL = "cluser_admin@HDP01.LOCAL";
@@ -235,32 +238,30 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
       }
     };
 
+    Capture<Name> capturedName = new Capture<Name>(CaptureType.ALL);
+    Capture<Attributes> capturedAttributes = new Capture<Attributes>(CaptureType.ALL);
+
     ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
 
+    NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
+    expect(searchResult.hasMore()).andReturn(false).once();
+
+    LdapContext ldapContext = createNiceMock(LdapContext.class);
+    expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
+        .andReturn(searchResult)
+        .once();
+
+    expect(ldapContext.createSubcontext(capture(capturedName), capture(capturedAttributes)))
+        .andReturn(createNiceMock(DirContext.class))
+        .anyTimes();
+
     expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
-        .andAnswer(new IAnswer<LdapContext>() {
-          @Override
-          public LdapContext answer() throws Throwable {
-            LdapContext ldapContext = createNiceMock(LdapContext.class);
-            expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
-                .andAnswer(new IAnswer<NamingEnumeration<SearchResult>>() {
-                  @Override
-                  public NamingEnumeration<SearchResult> answer() throws Throwable {
-                    NamingEnumeration<SearchResult> result = createNiceMock(NamingEnumeration.class);
-                    expect(result.hasMore()).andReturn(false).once();
-                    replay(result);
-                    return result;
-                  }
-                })
-                .once();
-            replay(ldapContext);
-            return ldapContext;
-          }
-        })
+        .andReturn(ldapContext)
         .once();
+
     expect(handler.createSearchControls()).andAnswer(new IAnswer<SearchControls>() {
       @Override
       public SearchControls answer() throws Throwable {
@@ -273,45 +274,67 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
     replayAll();
 
     handler.open(kc, DEFAULT_REALM, kerberosEnvMap);
+    handler.createPrincipal("nn/c6501.ambari.apache.org", "secret", true);
+    handler.createPrincipal("hdfs@" + DEFAULT_REALM, "secret", false);
+    handler.close();
 
-    Map<String, Object> context = new HashMap<String, Object>();
-    context.put("principal", "nn/c6501.ambari.apache.org");
-    context.put("principal_primary", "nn");
-    context.put("principal_instance", "c6501.ambari.apache.org");
-    context.put("realm", "EXAMPLE.COM");
-    context.put("realm_lowercase", "example.com");
-    context.put("password", "secret");
-    context.put("is_service", true);
-    context.put("container_dn", "ou=cluster,DC=EXAMPLE,DC=COM");
-
-    Map<String, Object> data;
-
-    data = handler.processCreateTemplate(context);
-
-    Assert.assertNotNull(data);
-    Assert.assertEquals(7, data.size());
-    Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("cn"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("servicePrincipalName"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
-    Assert.assertEquals("\"secret\"", data.get("unicodePwd"));
-    Assert.assertEquals("0", data.get("accountExpires"));
-    Assert.assertEquals("512", data.get("userAccountControl"));
-
-
-    context.put("is_service", false);
-    data = handler.processCreateTemplate(context);
-
-    Assert.assertNotNull(data);
-    Assert.assertEquals(6, data.size());
-    Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("cn"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
-    Assert.assertEquals("\"secret\"", data.get("unicodePwd"));
-    Assert.assertEquals("0", data.get("accountExpires"));
-    Assert.assertEquals("512", data.get("userAccountControl"));
+    List<Attributes> attributesList = capturedAttributes.getValues();
+    Attributes attributes;
 
-    handler.close();
+    attributes = attributesList.get(0);
+    String[] objectClasses = new String[]{"top", "person", "organizationalPerson", "user"};
+
+    Assert.assertNotNull(attributes);
+    Assert.assertEquals(7, attributes.size());
+
+    Assert.assertNotNull(attributes.get("objectClass"));
+    Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+    for (int i = 0; i < objectClasses.length; i++) {
+      Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+    }
+
+    Assert.assertNotNull(attributes.get("cn"));
+    Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("cn").get());
+
+    Assert.assertNotNull(attributes.get("servicePrincipalName"));
+    Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("servicePrincipalName").get());
+
+    Assert.assertNotNull(attributes.get("userPrincipalName"));
+    Assert.assertEquals("nn/c6501.ambari.apache.org@hdp01.local", attributes.get("userPrincipalName").get());
+
+    Assert.assertNotNull(attributes.get("unicodePwd"));
+    Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+    Assert.assertNotNull(attributes.get("accountExpires"));
+    Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+    Assert.assertNotNull(attributes.get("userAccountControl"));
+    Assert.assertEquals("66048", attributes.get("userAccountControl").get());
+
+    attributes = attributesList.get(1);
+    Assert.assertNotNull(attributes);
+    Assert.assertEquals(6, attributes.size());
+
+    Assert.assertNotNull(attributes.get("objectClass"));
+    Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+    for (int i = 0; i < objectClasses.length; i++) {
+      Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+    }
+
+    Assert.assertNotNull(attributes.get("cn"));
+    Assert.assertEquals("hdfs", attributes.get("cn").get());
+
+    Assert.assertNotNull(attributes.get("userPrincipalName"));
+    Assert.assertEquals("hdfs@hdp01.local", attributes.get("userPrincipalName").get());
+
+    Assert.assertNotNull(attributes.get("unicodePwd"));
+    Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+    Assert.assertNotNull(attributes.get("accountExpires"));
+    Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+    Assert.assertNotNull(attributes.get("userAccountControl"));
+    Assert.assertEquals("66048", attributes.get("userAccountControl").get());
   }
 
   @Test
@@ -321,54 +344,52 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
       {
         put(ADKerberosOperationHandler.KERBEROS_ENV_LDAP_URL, DEFAULT_LDAP_URL);
         put(ADKerberosOperationHandler.KERBEROS_ENV_PRINCIPAL_CONTAINER_DN, DEFAULT_PRINCIPAL_CONTAINER_DN);
-        put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "{" +
+        put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "" +
+            "#set( $user = \"${principal_primary}-${principal_digest}\" )" +
+            "{" +
             "  \"objectClass\": [" +
             "    \"top\"," +
             "    \"person\"," +
             "    \"organizationalPerson\"," +
             "    \"user\"" +
             "  ]," +
-            "  \"cn\": \"$principal@$realm\"," +
-            "  \"dn\": \"$principal@$realm,$container_dn\"," +
-            "  \"distinguishedName\": \"$principal@$realm,$container_dn\"," +
-            "  \"sAMAccountName\": \"$principal\"," +
+            "  \"cn\": \"$user\"," +
+            "  \"sAMAccountName\": \"$user.substring(0,20)\"," +
             "  #if( $is_service )" +
-            "  \"servicePrincipalName\": \"$principal\"," +
+            "  \"servicePrincipalName\": \"$principal_name\"," +
             "  #end" +
-            "  \"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
-            "  \"unicodePwd\": \"`$password`\"," +
+            "  \"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+            "  \"unicodePwd\": \"$password\"," +
             "  \"accountExpires\": \"0\"," +
             "  \"userAccountControl\": \"66048\"" +
             "}");
       }
     };
 
+    Capture<Name> capturedName = new Capture<Name>();
+    Capture<Attributes> capturedAttributes = new Capture<Attributes>();
+
     ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
 
+    NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
+    expect(searchResult.hasMore()).andReturn(false).once();
+
+    LdapContext ldapContext = createNiceMock(LdapContext.class);
+    expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
+        .andReturn(searchResult)
+        .once();
+
+    expect(ldapContext.createSubcontext(capture(capturedName), capture(capturedAttributes)))
+        .andReturn(createNiceMock(DirContext.class))
+        .once();
+
     expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
-        .andAnswer(new IAnswer<LdapContext>() {
-          @Override
-          public LdapContext answer() throws Throwable {
-            LdapContext ldapContext = createNiceMock(LdapContext.class);
-            expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
-                .andAnswer(new IAnswer<NamingEnumeration<SearchResult>>() {
-                  @Override
-                  public NamingEnumeration<SearchResult> answer() throws Throwable {
-                    NamingEnumeration<SearchResult> result = createNiceMock(NamingEnumeration.class);
-                    expect(result.hasMore()).andReturn(false).once();
-                    replay(result);
-                    return result;
-                  }
-                })
-                .once();
-            replay(ldapContext);
-            return ldapContext;
-          }
-        })
+        .andReturn(ldapContext)
         .once();
+
     expect(handler.createSearchControls()).andAnswer(new IAnswer<SearchControls>() {
       @Override
       public SearchControls answer() throws Throwable {
@@ -381,34 +402,43 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
     replayAll();
 
     handler.open(kc, DEFAULT_REALM, kerberosEnvMap);
+    handler.createPrincipal("nn/c6501.ambari.apache.org", "secret", true);
+    handler.close();
 
+    Attributes attributes = capturedAttributes.getValue();
+    String[] objectClasses = new String[]{"top", "person", "organizationalPerson", "user"};
 
-    Map<String, Object> context = new HashMap<String, Object>();
-    context.put("principal", "nn/c6501.ambari.apache.org");
-    context.put("principal_primary", "nn");
-    context.put("principal_instance", "c6501.ambari.apache.org");
-    context.put("realm", "EXAMPLE.COM");
-    context.put("realm_lowercase", "example.com");
-    context.put("password", "secret");
-    context.put("is_service", true);
-    context.put("container_dn", "ou=cluster,DC=EXAMPLE,DC=COM");
-
-    Map<String, Object> data = handler.processCreateTemplate(context);
-
-    Assert.assertNotNull(data);
-    Assert.assertEquals(10, data.size());
-    Assert.assertEquals(new ArrayList<String>(Arrays.asList("top", "person", "organizationalPerson", "user")), data.get("objectClass"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM", data.get("cn"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("servicePrincipalName"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@example.com", data.get("userPrincipalName"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org", data.get("sAMAccountName"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM,ou=cluster,DC=EXAMPLE,DC=COM", data.get("distinguishedName"));
-    Assert.assertEquals("nn/c6501.ambari.apache.org@EXAMPLE.COM,ou=cluster,DC=EXAMPLE,DC=COM", data.get("dn"));
-    Assert.assertEquals("`secret`", data.get("unicodePwd"));
-    Assert.assertEquals("0", data.get("accountExpires"));
-    Assert.assertEquals("66048", data.get("userAccountControl"));
+    Assert.assertNotNull(attributes);
+    Assert.assertEquals(8, attributes.size());
+
+    Assert.assertNotNull(attributes.get("objectClass"));
+    Assert.assertEquals(objectClasses.length, attributes.get("objectClass").size());
+    for (int i = 0; i < objectClasses.length; i++) {
+      Assert.assertEquals(objectClasses[i], attributes.get("objectClass").get(i));
+    }
+
+    Assert.assertNotNull(attributes.get("cn"));
+    Assert.assertEquals("nn-995e1580db28198e7fda1417ab5d894c877937d2", attributes.get("cn").get());
+
+    Assert.assertNotNull(attributes.get("servicePrincipalName"));
+    Assert.assertEquals("nn/c6501.ambari.apache.org", attributes.get("servicePrincipalName").get());
+
+    Assert.assertNotNull(attributes.get("userPrincipalName"));
+    Assert.assertEquals("nn/c6501.ambari.apache.org@hdp01.local", attributes.get("userPrincipalName").get());
+
+    Assert.assertNotNull(attributes.get("sAMAccountName"));
+    Assert.assertTrue(attributes.get("sAMAccountName").get().toString().length() <= 20);
+    Assert.assertEquals("nn-995e1580db28198e7", attributes.get("sAMAccountName").get());
+
+    Assert.assertNotNull(attributes.get("unicodePwd"));
+    Assert.assertEquals("\"secret\"", new String((byte[]) attributes.get("unicodePwd").get(), Charset.forName("UTF-16LE")));
+
+    Assert.assertNotNull(attributes.get("accountExpires"));
+    Assert.assertEquals("0", attributes.get("accountExpires").get());
+
+    Assert.assertNotNull(attributes.get("userAccountControl"));
+    Assert.assertEquals("66048", attributes.get("userAccountControl").get());
 
-    handler.close();
   }
 
   /**
@@ -458,31 +488,40 @@ public class ADKerberosOperationHandlerTest extends EasyMockSupport {
     // does the principal already exist?
     System.out.println("Principal exists: " + handler.principalExists("nn/c1508.ambari.apache.org"));
 
-    //create principal
-//    handler.createPrincipal("nn/c1508.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword(), true);
-
     handler.close();
 
-    kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE, "{" +
-        "\"objectClass\": [\"top\", \"person\", \"organizationalPerson\", \"user\"]," +
-        "\"distinguishedName\": \"CN=$principal@$realm,$container_dn\"," +
-        "#if( $is_service )" +
-        "\"servicePrincipalName\": \"$principal\"," +
-        "#end" +
-        "\"userPrincipalName\": \"$principal@$realm.toLowerCase()\"," +
-        "\"unicodePwd\": \"\\\"$password\\\"\"," +
-        "\"accountExpires\": \"0\"," +
-        "\"userAccountControl\": \"66048\"" +
-        "}");
+    kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_CREATE_ATTRIBUTES_TEMPLATE,
+        "#set( $user = \"${principal_primary}-${principal_digest}\" )" +
+            "{" +
+            "  \"objectClass\": [" +
+            "    \"top\"," +
+            "    \"person\"," +
+            "    \"organizationalPerson\"," +
+            "    \"user\"" +
+            "  ]," +
+            "  \"cn\": \"$user\"," +
+            "  \"sAMAccountName\": \"$user.substring(0,20)\"," +
+            "  #if( $is_service )" +
+            "  \"servicePrincipalName\": \"$principal_name\"," +
+            "  #end" +
+            "  \"userPrincipalName\": \"$normalized_principal.toLowerCase()\"," +
+            "  \"unicodePwd\": \"$password\"," +
+            "  \"accountExpires\": \"0\"," +
+            "  \"userAccountControl\": \"66048\"" +
+            "}"
+    );
 
     handler.open(credentials, realm, kerberosEnvMap);
+
+    // remove the principal
+    handler.removePrincipal("abcdefg");
+    handler.removePrincipal("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM);
+
     handler.createPrincipal("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword(), true);
+    handler.createPrincipal("abcdefg@" + DEFAULT_REALM, handler.createSecurePassword(), false);
 
     //update the password
-    handler.setPrincipalPassword("nn/c1508.ambari.apache.org", handler.createSecurePassword());
-
-    // remove the principal
-    // handler.removeServicePrincipal("nn/c1508.ambari.apache.org");
+    handler.setPrincipalPassword("abcdefg/c1509.ambari.apache.org@" + DEFAULT_REALM, handler.createSecurePassword());
 
     handler.close();
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/9f291484/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
new file mode 100644
index 0000000..28dd08a
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/DeconstructedPrincipalTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.serveraction.kerberos;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class DeconstructedPrincipalTest {
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testNullPrincipal() throws Exception {
+    DeconstructedPrincipal.valueOf(null, null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testEmptyPrincipal() throws Exception {
+    DeconstructedPrincipal.valueOf("", null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testInvalidPrincipal() throws Exception {
+    DeconstructedPrincipal.valueOf("/invalid", null);
+  }
+
+  @Test
+  public void testPrimary() throws Exception {
+    DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary", "REALM");
+
+    assertNotNull(deconstructedPrincipal);
+    assertEquals("primary", deconstructedPrincipal.getPrimary());
+    assertNull(deconstructedPrincipal.getInstance());
+    assertEquals("REALM", deconstructedPrincipal.getRealm());
+    assertEquals("primary", deconstructedPrincipal.getPrincipalName());
+    assertEquals("primary@REALM", deconstructedPrincipal.getNormalizedPrincipal());
+  }
+
+  @Test
+  public void testPrimaryRealm() throws Exception {
+    DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary@MYREALM", "REALM");
+
+    assertNotNull(deconstructedPrincipal);
+    assertEquals("primary", deconstructedPrincipal.getPrimary());
+    assertNull(deconstructedPrincipal.getInstance());
+    assertEquals("MYREALM", deconstructedPrincipal.getRealm());
+    assertEquals("primary", deconstructedPrincipal.getPrincipalName());
+    assertEquals("primary@MYREALM", deconstructedPrincipal.getNormalizedPrincipal());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testInstance() throws Exception {
+    DeconstructedPrincipal.valueOf("/instance", "REALM");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testInstanceRealm() throws Exception {
+    DeconstructedPrincipal.valueOf("/instance@MYREALM", "REALM");
+  }
+
+  @Test
+  public void testPrimaryInstance() throws Exception {
+    DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary/instance", "REALM");
+
+    assertNotNull(deconstructedPrincipal);
+    assertEquals("primary", deconstructedPrincipal.getPrimary());
+    assertEquals("instance", deconstructedPrincipal.getInstance());
+    assertEquals("instance", deconstructedPrincipal.getInstance());
+    assertEquals("REALM", deconstructedPrincipal.getRealm());
+    assertEquals("primary/instance", deconstructedPrincipal.getPrincipalName());
+    assertEquals("primary/instance@REALM", deconstructedPrincipal.getNormalizedPrincipal());
+  }
+
+  @Test
+  public void testPrimaryInstanceRealm() throws Exception {
+    DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("primary/instance@MYREALM", "REALM");
+
+    assertNotNull(deconstructedPrincipal);
+    assertEquals("primary", deconstructedPrincipal.getPrimary());
+    assertEquals("instance", deconstructedPrincipal.getInstance());
+    assertEquals("MYREALM", deconstructedPrincipal.getRealm());
+    assertEquals("primary/instance", deconstructedPrincipal.getPrincipalName());
+    assertEquals("primary/instance@MYREALM", deconstructedPrincipal.getNormalizedPrincipal());
+  }
+
+  @Test
+  public void testOddCharacters() throws Exception {
+    DeconstructedPrincipal deconstructedPrincipal = DeconstructedPrincipal.valueOf("p_ri.ma-ry/i.n_s-tance@M_Y-REALM.COM", "REALM");
+
+    assertNotNull(deconstructedPrincipal);
+    assertEquals("p_ri.ma-ry", deconstructedPrincipal.getPrimary());
+    assertEquals("i.n_s-tance", deconstructedPrincipal.getInstance());
+    assertEquals("M_Y-REALM.COM", deconstructedPrincipal.getRealm());
+    assertEquals("p_ri.ma-ry/i.n_s-tance", deconstructedPrincipal.getPrincipalName());
+    assertEquals("p_ri.ma-ry/i.n_s-tance@M_Y-REALM.COM", deconstructedPrincipal.getNormalizedPrincipal());
+  }
+
+}
\ No newline at end of file