Java tutorial
/** * 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.hadoop.security.authentication.util; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class implements parsing and handling of Kerberos principal names. In * particular, it splits them apart and translates them down into local * operating system names. */ @SuppressWarnings("all") @InterfaceAudience.LimitedPrivate({ "HDFS", "MapReduce" }) @InterfaceStability.Evolving public class KerberosName { private static final Logger LOG = LoggerFactory.getLogger(KerberosName.class); /** * Constant that defines auth_to_local legacy hadoop evaluation */ public static final String MECHANISM_HADOOP = "hadoop"; /** * Constant that defines auth_to_local MIT evaluation */ public static final String MECHANISM_MIT = "mit"; /** Constant that defines the default behavior of the rule mechanism */ public static final String DEFAULT_MECHANISM = MECHANISM_HADOOP; /** The first component of the name */ private final String serviceName; /** The second component of the name. It may be null. */ private final String hostName; /** The realm of the name. */ private final String realm; /** * A pattern that matches a Kerberos name with at most 2 components. */ private static final Pattern nameParser = Pattern.compile("([^/@]+)(/([^/@]+))?(@([^/@]+))?"); /** * A pattern that matches a string with out '$' and then a single * parameter with $n. */ private static Pattern parameterPattern = Pattern.compile("([^$]*)(\\$(\\d*))?"); /** * A pattern for parsing a auth_to_local rule. */ private static final Pattern ruleParser = Pattern.compile( "\\s*((DEFAULT)|(RULE:\\[(\\d*):([^\\]]*)](\\(([^)]*)\\))?" + "(s/([^/]*)/([^/]*)/(g)?)?))/?(L)?"); /** * A pattern that recognizes simple/non-simple names. */ private static final Pattern nonSimplePattern = Pattern.compile("[/@]"); /** * The list of translation rules. */ private static List<Rule> rules; /** * How to evaluate auth_to_local rules */ private static String ruleMechanism = null; private static String defaultRealm = null; @VisibleForTesting public static void resetDefaultRealm() { try { defaultRealm = KerberosUtil.getDefaultRealm(); } catch (Exception ke) { LOG.debug("resetting default realm failed, " + "current default realm will still be used.", ke); } } /** * Create a name from the full Kerberos principal name. * @param name full Kerberos principal name. */ public KerberosName(String name) { Matcher match = nameParser.matcher(name); if (!match.matches()) { if (name.contains("@")) { throw new IllegalArgumentException("Malformed Kerberos name: " + name); } else { serviceName = name; hostName = null; realm = null; } } else { serviceName = match.group(1); hostName = match.group(3); realm = match.group(5); } } /** * Get the configured default realm. * Used syncronized method here, because double-check locking is overhead. * @return the default realm from the krb5.conf */ public static synchronized String getDefaultRealm() { if (defaultRealm == null) { try { defaultRealm = KerberosUtil.getDefaultRealm(); } catch (Exception ke) { LOG.debug("Kerberos krb5 configuration not found, setting default realm to empty"); defaultRealm = ""; } } return defaultRealm; } /** * Put the name back together from the parts. */ @Override public String toString() { StringBuilder result = new StringBuilder(); result.append(serviceName); if (hostName != null) { result.append('/'); result.append(hostName); } if (realm != null) { result.append('@'); result.append(realm); } return result.toString(); } /** * Get the first component of the name. * @return the first section of the Kerberos principal name */ public String getServiceName() { return serviceName; } /** * Get the second component of the name. * @return the second section of the Kerberos principal name, and may be null */ public String getHostName() { return hostName; } /** * Get the realm of the name. * @return the realm of the name, may be null */ public String getRealm() { return realm; } /** * An encoding of a rule for translating kerberos names. */ private static class Rule { private final boolean isDefault; private final int numOfComponents; private final String format; private final Pattern match; private final Pattern fromPattern; private final String toPattern; private final boolean repeat; private final boolean toLowerCase; Rule() { isDefault = true; numOfComponents = 0; format = null; match = null; fromPattern = null; toPattern = null; repeat = false; toLowerCase = false; } Rule(int numOfComponents, String format, String match, String fromPattern, String toPattern, boolean repeat, boolean toLowerCase) { isDefault = false; this.numOfComponents = numOfComponents; this.format = format; this.match = match == null ? null : Pattern.compile(match); this.fromPattern = fromPattern == null ? null : Pattern.compile(fromPattern); this.toPattern = toPattern; this.repeat = repeat; this.toLowerCase = toLowerCase; } @Override public String toString() { StringBuilder buf = new StringBuilder(); if (isDefault) { buf.append("DEFAULT"); } else { buf.append("RULE:["); buf.append(numOfComponents); buf.append(':'); buf.append(format); buf.append(']'); if (match != null) { buf.append('('); buf.append(match); buf.append(')'); } if (fromPattern != null) { buf.append("s/"); buf.append(fromPattern); buf.append('/'); buf.append(toPattern); buf.append('/'); if (repeat) { buf.append('g'); } } if (toLowerCase) { buf.append("/L"); } } return buf.toString(); } /** * Replace the numbered parameters of the form $n where n is from 1 to * the length of params. Normal text is copied directly and $n is replaced * by the corresponding parameter. * @param format the string to replace parameters again * @param params the list of parameters * @return the generated string with the parameter references replaced. * @throws BadFormatString */ static String replaceParameters(String format, String[] params) throws BadFormatString { Matcher match = parameterPattern.matcher(format); int start = 0; StringBuilder result = new StringBuilder(); while (start < format.length() && match.find(start)) { result.append(match.group(1)); String paramNum = match.group(3); if (paramNum != null) { try { int num = Integer.parseInt(paramNum); if (num < 0 || num > params.length) { throw new BadFormatString("index " + num + " from " + format + " is outside of the valid range 0 to " + (params.length - 1)); } result.append(params[num]); } catch (NumberFormatException nfe) { throw new BadFormatString("bad format in username mapping in " + paramNum, nfe); } } start = match.end(); } return result.toString(); } /** * Replace the matches of the from pattern in the base string with the value * of the to string. * @param base the string to transform * @param from the pattern to look for in the base string * @param to the string to replace matches of the pattern with * @param repeat whether the substitution should be repeated * @return */ static String replaceSubstitution(String base, Pattern from, String to, boolean repeat) { Matcher match = from.matcher(base); if (repeat) { return match.replaceAll(to); } else { return match.replaceFirst(to); } } /** * Try to apply this rule to the given name represented as a parameter * array. * @param params first element is the realm, second and later elements are * are the components of the name "a/b@FOO" -> {"FOO", "a", "b"} * @param ruleMechanism defines the rule evaluation mechanism * @return the short name if this rule applies or null * @throws IOException throws if something is wrong with the rules */ String apply(String[] params, String ruleMechanism) throws IOException { String result = null; if (isDefault) { if (getDefaultRealm().equals(params[0])) { result = params[1]; } } else if (params.length - 1 == numOfComponents) { String base = replaceParameters(format, params); if (match == null || match.matcher(base).matches()) { if (fromPattern == null) { result = base; } else { result = replaceSubstitution(base, fromPattern, toPattern, repeat); } } } if (result != null && nonSimplePattern.matcher(result).find() && ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) { throw new NoMatchingRule("Non-simple name " + result + " after auth_to_local rule " + this); } if (toLowerCase && result != null) { result = result.toLowerCase(Locale.ENGLISH); } return result; } } static List<Rule> parseRules(String rules) { List<Rule> result = new ArrayList<Rule>(); String remaining = rules.trim(); while (remaining.length() > 0) { Matcher matcher = ruleParser.matcher(remaining); if (!matcher.lookingAt()) { throw new IllegalArgumentException("Invalid rule: " + remaining); } if (matcher.group(2) != null) { result.add(new Rule()); } else { result.add(new Rule(Integer.parseInt(matcher.group(4)), matcher.group(5), matcher.group(7), matcher.group(9), matcher.group(10), "g".equals(matcher.group(11)), "L".equals(matcher.group(12)))); } remaining = remaining.substring(matcher.end()); } return result; } @SuppressWarnings("serial") public static class BadFormatString extends IOException { BadFormatString(String msg) { super(msg); } BadFormatString(String msg, Throwable err) { super(msg, err); } } @SuppressWarnings("serial") public static class NoMatchingRule extends IOException { NoMatchingRule(String msg) { super(msg); } } /** * Get the translation of the principal name into an operating system * user name. * @return the short name * @throws IOException throws if something is wrong with the rules */ public String getShortName() throws IOException { String[] params; if (hostName == null) { // if it is already simple, just return it if (realm == null) { return serviceName; } params = new String[] { realm, serviceName }; } else { params = new String[] { realm, serviceName, hostName }; } String ruleMechanism = this.ruleMechanism; if (ruleMechanism == null && rules != null) { LOG.warn("auth_to_local rule mechanism not set." + "Using default of " + DEFAULT_MECHANISM); ruleMechanism = DEFAULT_MECHANISM; } for (Rule r : rules) { String result = r.apply(params, ruleMechanism); if (result != null) { return result; } } if (ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) { throw new NoMatchingRule("No rules applied to " + toString()); } return toString(); } /** * Get the rules. * @return String of configured rules, or null if not yet configured */ public static String getRules() { String ruleString = null; if (rules != null) { StringBuilder sb = new StringBuilder(); for (Rule rule : rules) { sb.append(rule.toString()).append("\n"); } ruleString = sb.toString().trim(); } return ruleString; } /** * Indicates if the name rules have been set. * * @return if the name rules have been set. */ public static boolean hasRulesBeenSet() { return rules != null; } /** * Indicates of the rule mechanism has been set * * @return if the rule mechanism has been set. */ public static boolean hasRuleMechanismBeenSet() { return ruleMechanism != null; } /** * Set the rules. * @param ruleString the rules string. */ public static void setRules(String ruleString) { rules = (ruleString != null) ? parseRules(ruleString) : null; } /** * * @param ruleMech the evaluation type: hadoop, mit * 'hadoop' indicates '@' or '/' are not allowed the result * evaluation. 'MIT' indicates that auth_to_local * rules follow MIT Kerberos evaluation. */ public static void setRuleMechanism(String ruleMech) { if (ruleMech != null && (!ruleMech.equalsIgnoreCase(MECHANISM_HADOOP) && !ruleMech.equalsIgnoreCase(MECHANISM_MIT))) { throw new IllegalArgumentException("Invalid rule mechanism: " + ruleMech); } ruleMechanism = ruleMech; } /** * Get the rule evaluation mechanism * @return the rule evaluation mechanism */ public static String getRuleMechanism() { return ruleMechanism; } static void printRules() throws IOException { int i = 0; for (Rule r : rules) { System.out.println(++i + " " + r); } } }