blob: e959f65b0bd43de0f949c9865f9a3c22b449bfca [file] [log] [blame]
/**
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.hadoop.security.authentication.util;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
*/
public class AuthToken implements Principal {
/**
* Constant that identifies an anonymous request.
*/
private static final String ATTR_SEPARATOR = "&";
private static final String USER_NAME = "u";
private static final String PRINCIPAL = "p";
private static final String MAX_INACTIVES = "i";
private static final String EXPIRES = "e";
private static final String TYPE = "t";
private final static Set<String> ATTRIBUTES =
new HashSet<>(Arrays.asList(USER_NAME, PRINCIPAL, EXPIRES, TYPE));
private String userName;
private String principal;
private String type;
private long maxInactives;
private long expires;
private String tokenStr;
protected AuthToken() {
userName = null;
principal = null;
type = null;
maxInactives = -1;
expires = -1;
tokenStr = "ANONYMOUS";
generateToken();
}
private static final String ILLEGAL_ARG_MSG = " is NULL, empty or contains a '" + ATTR_SEPARATOR + "'";
/**
* Creates an authentication token.
*
* @param userName user name.
* @param principal principal (commonly matches the user name, with Kerberos is the full/long principal
* name while the userName is the short name).
* @param type the authentication mechanism name.
* (<code>System.currentTimeMillis() + validityPeriod</code>).
*/
public AuthToken(String userName, String principal, String type) {
checkForIllegalArgument(userName, "userName");
checkForIllegalArgument(principal, "principal");
checkForIllegalArgument(type, "type");
this.userName = userName;
this.principal = principal;
this.type = type;
this.maxInactives = -1;
this.expires = -1;
}
/**
* Check if the provided value is invalid. Throw an error if it is invalid, NOP otherwise.
*
* @param value the value to check.
* @param name the parameter name to use in an error message if the value is invalid.
*/
protected static void checkForIllegalArgument(String value, String name) {
if (value == null || value.length() == 0 || value.contains(ATTR_SEPARATOR)) {
throw new IllegalArgumentException(name + ILLEGAL_ARG_MSG);
}
}
/**
* Sets the max inactive interval of the token.
*
* @param interval max inactive interval of the token in milliseconds since
* the epoch.
*/
public void setMaxInactives(long interval) {
this.maxInactives = interval;
}
/**
* Sets the expiration of the token.
*
* @param expires expiration time of the token in milliseconds since the epoch.
*/
public void setExpires(long expires) {
this.expires = expires;
generateToken();
}
/**
* Returns true if the token has expired.
*
* @return true if the token has expired.
*/
public boolean isExpired() {
return (getMaxInactives() != -1 &&
System.currentTimeMillis() > getMaxInactives())
|| (getExpires() != -1 &&
System.currentTimeMillis() > getExpires());
}
/**
* Generates the token.
*/
private void generateToken() {
StringBuffer sb = new StringBuffer();
sb.append(USER_NAME).append("=").append(getUserName()).append(ATTR_SEPARATOR);
sb.append(PRINCIPAL).append("=").append(getName()).append(ATTR_SEPARATOR);
sb.append(TYPE).append("=").append(getType()).append(ATTR_SEPARATOR);
if (getMaxInactives() != -1) {
sb.append(MAX_INACTIVES).append("=")
.append(getMaxInactives()).append(ATTR_SEPARATOR);
}
sb.append(EXPIRES).append("=").append(getExpires());
tokenStr = sb.toString();
}
/**
* Returns the user name.
*
* @return the user name.
*/
public String getUserName() {
return userName;
}
/**
* Returns the principal name (this method name comes from the JDK {@link Principal} interface).
*
* @return the principal name.
*/
@Override
public String getName() {
return principal;
}
/**
* Returns the authentication mechanism of the token.
*
* @return the authentication mechanism of the token.
*/
public String getType() {
return type;
}
/**
* Returns the max inactive time of the token.
*
* @return the max inactive time of the token, in milliseconds since Epoc.
*/
public long getMaxInactives() {
return maxInactives;
}
/**
* Returns the expiration time of the token.
*
* @return the expiration time of the token, in milliseconds since Epoc.
*/
public long getExpires() {
return expires;
}
/**
* Returns the string representation of the token.
* <p>
* This string representation is parseable by the {@link #parse} method.
*
* @return the string representation of the token.
*/
@Override
public String toString() {
return tokenStr;
}
public static AuthToken parse(String tokenStr) throws AuthenticationException {
if (tokenStr.length() >= 2) {
// strip the \" at the two ends of the tokenStr
if (tokenStr.charAt(0) == '\"' &&
tokenStr.charAt(tokenStr.length()-1) == '\"') {
tokenStr = tokenStr.substring(1, tokenStr.length()-1);
}
}
Map<String, String> map = split(tokenStr);
// remove the signature part, since client doesn't care about it
map.remove("s");
if (!map.keySet().containsAll(ATTRIBUTES)) {
throw new AuthenticationException("Invalid token string, missing attributes");
}
long expires = Long.parseLong(map.get(EXPIRES));
AuthToken token = new AuthToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE));
//process optional attributes
if (map.containsKey(MAX_INACTIVES)) {
long maxInactives = Long.parseLong(map.get(MAX_INACTIVES));
token.setMaxInactives(maxInactives);
}
token.setExpires(expires);
return token;
}
/**
* Splits the string representation of a token into attributes pairs.
*
* @param tokenStr string representation of a token.
*
* @return a map with the attribute pairs of the token.
*
* @throws AuthenticationException thrown if the string representation of the token could not be broken into
* attribute pairs.
*/
private static Map<String, String> split(String tokenStr) throws AuthenticationException {
Map<String, String> map = new HashMap<String, String>();
StringTokenizer st = new StringTokenizer(tokenStr, ATTR_SEPARATOR);
while (st.hasMoreTokens()) {
String part = st.nextToken();
int separator = part.indexOf('=');
if (separator == -1) {
throw new AuthenticationException("Invalid authentication token");
}
String key = part.substring(0, separator);
String value = part.substring(separator + 1);
map.put(key, value);
}
return map;
}
}