package net.etalia.crepuscolo.auth;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.etalia.crepuscolo.codec.Token;
import net.etalia.crepuscolo.codec.Token.TokenReader;
import net.etalia.crepuscolo.utils.Beans;
import net.etalia.crepuscolo.utils.Strings;


/**
 * Creates and parses auth tokens.
 * <p>
 * Auth tokens are creating using {@link Token} class, and use following format :
 * <ol>
 *   <li>UserId, string, not mandatory (may be blank) if a systemid is specified</li>
 *   <li>Timestamp, seconds till epoch, mandatory</li>
 *   <li>SystemId, string, not mandatory (may be blank or absent) is a userId is specified</li>
 *   <li>md5 sum with salt, as specified in {@link Token}</li>
 * </ol>
 * </p>
 * <p>
 * A simple token will contain a userid and a timestamp. That is already enough to verify, together
 * with the md5 salted signature, that the token is valid and retrieve the user. The timestamp
 * will later be used in {@link AuthServiceImpl} to check validity depending on the desired 
 * verification level.
 * </p>
 * <p>
 * Another scenario is when an internal system is calling another internal system. In that case
 * the systemid token will be populated. If a userid is specified, then the system is asking to pretend
 * to be that user, and will be authenticated as that user with maximum level. If a userid is not
 * specified, then the system is simply authenticating itself to the other service.
 * </p>
 * <p>
 * Since systemid is so powerful and is intended to be used only inside the lan/vpn of the datacenter,
 * it is considered valid only if :
 * <ul>
 *   <li>The md5 is valid using a different salt than the "user only" tokens</li>
 *   <li>The connection was received on a valid port, a port which should blocked at firewall level and
 *   accessible only from internal services (see {@link AuthFilter})</li>
 * </ul>
 * </p>
 * 
 * @author Simone Gianni <simoneg@apache.org>
 */
public class AuthData {

	private static final String STATIC_SALT = "STSALT";

	protected final static Logger log = Logger.getLogger(AuthData.class.getName());

	public enum Fields {
		UserId("ui"),
		TimeStamp("ts"),
		Https("hs"),
		SystemId("sid"),
		UserPassword("up"),
		Forced("f");
		
		private String fname;

		private Fields(String fname) {
			this.fname = fname;
		}
		
		public String fieldName() {
			return fname;
		}
	}

	private Map<String,Object> incoming = new HashMap<String, Object>();
	private String currentToken;

	public AuthData(String header, boolean validPort, boolean https) {
		log.log(Level.FINE, "Authenticating with {} validPort:{} https:{}", new Object[] { header, validPort, https});
		if (header != null) {
			this.currentToken = header;
			if (header.startsWith("Etalia ")) header = header.substring(7);
			TokenReader parser = Token.parse(header);
			incoming.put(Fields.UserId.fname, Strings.nullIfBlank(parser.getString()));
			incoming.put(Fields.TimeStamp.fname, parser.getLong());
			incoming.put(Fields.SystemId.fname, Strings.nullIfBlank(parser.getString()));
			incoming.put(Fields.UserPassword.fname, Strings.nullIfBlank(parser.getString()));
			parser.checkValid(STATIC_SALT);
		}
		// Put https
		incoming.put(Fields.Https.fname, https);
		
		log.log(Level.FINE, "Auth data : {}", incoming);
		
		// Check systemid only on valid ports
		if (incoming.get(Fields.SystemId.fname) != null) {
			if (!validPort) {
				throw new IllegalArgumentException("Cannot support systemId on non valid port");
			}
			// TODO check token timestamp validity?
			
			// Setup timestamp to pretend the user is always valid
			incoming.put(Fields.TimeStamp.fname, System.currentTimeMillis());
		}
	}

	public String getString(Fields field) {
		return (String)incoming.get(field.fname);
	}

	public Boolean getBoolean(Fields field) {
		return (Boolean)incoming.get(field.fname);
	}

	public Long getLong(Fields field) {
		return (Long)incoming.get(field.fname);
	}

	public String getUserId() {
		return getString(Fields.UserId);
	}

	public void forceUserId(String userid) {
		incoming.put(Fields.UserId.fname, userid);
		incoming.put(Fields.TimeStamp.fname, System.currentTimeMillis());
		incoming.put(Fields.Forced.fname, true);
	}

	public String getSystemId() {
		return getString(Fields.SystemId);
	}

	public String getUserPassword() {
		return getString(Fields.UserPassword);
	}

	public boolean isForced() {
		return Beans.has(getBoolean(Fields.Forced));
	}

	public long getTimeStamp() {
		Long ts = getLong(Fields.TimeStamp);
		if (ts == null) return 0;
		return ts * 1000;
	}

	public static String produce(String userId, String profileId, String userPassword, String systemId) {
		return "Crepuscolo " + 
		Token.create()
		.addString(userId)
		.addString(profileId)
		.addLong(System.currentTimeMillis() / 1000)
		.addString(systemId)
		.addString(userPassword)
		.addSalt(STATIC_SALT)
		.encode();
	}

	public static String produceForUser(String userId, String userPassword) {
		return produce(userId, "", userPassword, "");
	}

	public static String produceForUserAndProfile(String userId, String profileId, String userPassword) {
		return produce(userId, profileId, userPassword, "");
	}

	public static String produceForSystem(String systemId, String asUserId) {
		if (asUserId == null) asUserId = "";
		return produce(asUserId, "", "", systemId);
	}

	public String getCurrentToken() {
		return currentToken;
	}

}

@@ -2020,22 +2020,21 @@
 %0A%0A%09p
-rivate
+ublic
  static 
 fina
@@ -2029,22 +2029,16 @@
  static 
-final 
 String S
@@ -2051,19 +2051,51 @@
 SALT
- = %22STSALT%22
+;%0A%09public static String AUTHENTICATION_SIGN
 ;%0A%0A%09
@@ -2815,15 +2815,31 @@
 ith(
-%22Etalia
+AUTHENTICATION_SIGN + %22
  %22))
@@ -4689,34 +4689,16 @@
  userId,
- String profileId,
  String 
@@ -4743,19 +4743,31 @@
 urn 
-%22Crepuscolo
+AUTHENTICATION_SIGN + %22
  %22 +
@@ -4810,32 +4810,8 @@
 Id)%0A
-%09%09.addString(profileId)%0A
 %09%09.a
@@ -5046,174 +5046,8 @@
 rId,
- %22%22, userPassword, %22%22);%0A%09%7D%0A%0A%09public static String produceForUserAndProfile(String userId, String profileId, String userPassword) %7B%0A%09%09return produce(userId, profileId,
  use
@@ -5180,16 +5180,16 @@
 d = %22%22;%0A
+
 %09%09return
@@ -5210,20 +5210,16 @@
 rId, %22%22,
- %22%22,
  systemI

Improve AuthData