package io.robe.auth.token; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; import io.robe.auth.Credentials; import io.robe.auth.token.configuration.TokenBasedAuthConfiguration; import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; import org.joda.time.DateTime; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * A basic token implementation. Uses jasypt for encrypt & decrypt operations. * Takes all properties from configuration. Uses Guava for permission caching. * All cached permission entries will live with token. */ public class BasicToken implements Credentials { private static final PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); private static final String SEPARATOR = "--"; private static int defaultMaxAge; private static Cache<String, Set<String>> cache; private String userId; private String username; private DateTime expireAt; private String attributesHash; private String tokenString; private int maxAge; /** * Creates an access token with the given parameters. * * @param username Username * @param expireAt expiration time of token * @param attributes extra attributes to customize token * @return an instance */ public BasicToken(String userId, String username, DateTime expireAt, Map<String, String> attributes) { this.userId = userId; this.username = username; this.expireAt = expireAt; this.maxAge = defaultMaxAge; generateAttributesHash(attributes); } /** * Creates an access token with the given tokenString. * * @param tokenString to parse * @return and instance * @throws Exception throws in case of failing opening token */ public BasicToken(String tokenString) throws Exception { tokenString = tokenString.replaceAll("\"", ""); tokenString = new String(BaseEncoding.base16().decode(tokenString)); String[] parts = encryptor.decrypt(tokenString).split(SEPARATOR); this.userId = parts[0]; this.username = parts[1]; this.expireAt = new DateTime(Long.valueOf(parts[2])); this.attributesHash = parts[3]; } /** * Configure method for Token generation configurations and encryptor configure * * @param configuration confiuration for auth bundle */ public static void configure(TokenBasedAuthConfiguration configuration) { encryptor.setPoolSize(configuration.getPoolSize()); // This would be a good value for a 4-core system if (configuration.getServerPassword().equals("auto")) { encryptor.setPassword(UUID.randomUUID().toString()); } else { encryptor.setPassword(configuration.getServerPassword()); } encryptor.setAlgorithm(configuration.getAlgorithm()); encryptor.initialize(); BasicToken.defaultMaxAge = configuration.getMaxage(); //Create cache for permissions. cache = CacheBuilder.newBuilder() .expireAfterAccess(defaultMaxAge, TimeUnit.SECONDS) .expireAfterWrite(defaultMaxAge, TimeUnit.SECONDS) .build(); } public static void clearPermissionCache(String username) { cache.invalidate(username); cache.cleanUp(); } public static void clearAllPermissionCache() { cache.invalidateAll(); cache.cleanUp(); } public static Set<String> getCurrentUsernames() { cache.cleanUp(); return cache.asMap().keySet(); } @Override public String getUserId() { return userId; } @Override public String getUsername() { return username; } public boolean isExpired() { return !expireAt.isAfterNow(); } public void setExpiration(int durationInSeconds) { expireAt = DateTime.now().plusSeconds(durationInSeconds); resetTokenString(); } public Date getExpirationDate() { return expireAt.toDate(); } public String getAttributesHash() { return attributesHash; } /** * Generates attribute has with 'userAgent', 'remoteAddr' keys. * Combines them and hashes with SHA256 and sets the variable. * * @param attributes */ private void generateAttributesHash(Map<String, String> attributes) { StringBuilder attr = new StringBuilder(); attr.append(attributes.get("userAgent")); // attr.append(attributes.get("remoteAddr")); TODO: add remote ip address after you find how to get remote IP from HttpContext attributesHash = Hashing.sha256().hashString(attr.toString(), StandardCharsets.UTF_8).toString(); resetTokenString(); } public String getTokenString() throws Exception { return tokenString == null ? generateTokenString() : tokenString; } /** * Generates a tokenString with a new expiration date and assigns it. * * @return new tokenString * @throws Exception */ private String generateTokenString() throws Exception { //Renew age //Stringify token data StringBuilder dataString = new StringBuilder(); dataString .append(getUserId()) .append(SEPARATOR) .append(getUsername()) .append(SEPARATOR) .append(getExpirationDate().getTime()) .append(SEPARATOR) .append(attributesHash); // Encrypt token data string String newTokenString = encryptor.encrypt(dataString.toString()); newTokenString = BaseEncoding.base16().encode(newTokenString.getBytes()); tokenString = newTokenString; return newTokenString; } public int getMaxAge() { return maxAge < 1 ? defaultMaxAge : maxAge; } public void setMaxAge(int maxAge) { if (maxAge < 1) maxAge = defaultMaxAge; this.maxAge = maxAge; } public Set<String> getPermissions() { return cache.getIfPresent(getUsername()); } /** * Sets permissions to the cache with current username * * @param permissions permission list for the current user. */ public void setPermissions(Set<String> permissions) { cache.put(getUsername(), permissions); } private void resetTokenString() { tokenString = null; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BasicToken)) return false; BasicToken that = (BasicToken) o; if (!userId.equals(that.userId)) return false; if (!username.equals(that.username)) return false; return attributesHash.equals(that.attributesHash); } @Override public int hashCode() { int result = userId.hashCode(); result = 31 * result + username.hashCode(); result = 31 * result + attributesHash.hashCode(); return result; } public String getName() { return "BasicToken"; } public static PooledPBEStringEncryptor getEncryptor() { return encryptor; } }