blob: 9976818a687964d71f32e0b798fdc84076b6a57e [file] [log] [blame]
/**
* 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.pulsar.broker.authentication;
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;
import javax.naming.AuthenticationException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SaslRoleToken implements Principal {
/**
* Constant that identifies an anonymous request.
*/
public static final SaslRoleToken ANONYMOUS = new SaslRoleToken();
private static final String ATTR_SEPARATOR = "&";
private static final String USER_ROLE = "u";
private static final String EXPIRES = "e";
private static final String SESSION = "i";
private static final Set<String> ATTRIBUTES =
new HashSet<String>(Arrays.asList(USER_ROLE, EXPIRES, SESSION));
private String userRole;
private String session;
private long expires;
private String token;
private SaslRoleToken() {
userRole = null;
session = null;
expires = -1;
token = "ANONYMOUS";
generateToken();
}
private static final String ILLEGAL_ARG_MSG = " is NULL, empty or contains a '" + ATTR_SEPARATOR + "'";
/**
* Creates an authentication token.
*
* @param userRole user name.
* @param session the sessionId.
* (<code>System.currentTimeMillis() + validityPeriod</code>).
*/
public SaslRoleToken(String userRole, String session) {
checkForIllegalArgument(session, "session");
this.userRole = userRole;
this.session = session;
this.expires = -1;
generateToken();
}
public SaslRoleToken(String userRole, String session, long expires) {
checkForIllegalArgument(userRole, "userRole");
checkForIllegalArgument(session, "session");
this.userRole = userRole;
this.session = session;
this.expires = expires;
generateToken();
}
/**
* 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.
*/
private 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 expiration of the token.
*
* @param expires expiration time of the token in milliseconds since the epoch.
*/
public void setExpires(long expires) {
if (this != SaslRoleToken.ANONYMOUS) {
this.expires = expires;
generateToken();
}
}
/**
* Generates the token.
*/
private void generateToken() {
StringBuilder sb = new StringBuilder();
sb.append(USER_ROLE).append("=").append(getUserRole()).append(ATTR_SEPARATOR);
sb.append(SESSION).append("=").append(getSession()).append(ATTR_SEPARATOR);
sb.append(EXPIRES).append("=").append(getExpires());
token = sb.toString();
}
/**
* Returns the user name.
*
* @return the user name.
*/
public String getUserRole() {
return userRole;
}
/**
* Returns the principal name (this method name comes from the JDK {@link Principal} interface).
*
* @return the principal name.
*/
@Override
public String getName() {
return userRole;
}
/**
* Returns the authentication mechanism of the token.
*
* @return the authentication mechanism of the token.
*/
public String getSession() {
return session;
}
/**
* Returns the expiration time of the token.
*
* @return the expiration time of the token, in milliseconds since Epoc.
*/
public long getExpires() {
return expires;
}
/**
* Returns if the token has expired.
*
* @return if the token has expired.
*/
public boolean isExpired() {
return getExpires() != -1 && System.currentTimeMillis() > getExpires();
}
/**
* 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 token;
}
/**
* Parses a string into an authentication token.
*
* @param tokenStr string representation of a token.
*
* @return the parsed authentication token.
*
* @throws AuthenticationException thrown if the string representation could not be parsed into
* an authentication token.
*/
public static SaslRoleToken parse(String tokenStr) throws AuthenticationException {
Map<String, String> map = split(tokenStr);
if (!map.keySet().equals(ATTRIBUTES)) {
throw new AuthenticationException("Invalid token string, missing attributes");
}
long expires = Long.parseLong(map.get(EXPIRES));
SaslRoleToken token = new SaslRoleToken(map.get(USER_ROLE), map.get(SESSION));
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;
}
}