/*
 *   SakaiSigning.jws
 *
 */
 
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.Set;
import java.util.Collection;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.cover.SessionManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.sakaiproject.authz.cover.AuthzGroupService;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.Role;

import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.site.api.ToolConfiguration;

import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SitePage;

import org.sakaiproject.user.api.UserEdit;
import org.sakaiproject.user.api.User;

import org.sakaiproject.tool.cover.ToolManager;
import org.sakaiproject.site.cover.SiteService;

import org.sakaiproject.authz.cover.SecurityService;

import org.sakaiproject.service.gradebook.shared.GradebookService;
import org.sakaiproject.service.gradebook.shared.Assignment;
import org.sakaiproject.component.cover.ServerConfigurationService;

import java.util.Properties;
import java.util.Calendar;
import org.apache.axis.AxisFault;

import org.sakaiproject.util.Xml;
//import org.w3c.dom.Document;
//import org.w3c.dom.Element;
//import org.w3c.dom.Node;
//import org.w3c.dom.NodeList;

import java.io.*;

import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;

import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;


public class SakaiSigning {

    private static SecretKey salt;
    private static SecretKey privateKey;

    private static boolean verify(String data, String sign) throws Exception {
	if (salt == null)
	   salt = readSecretKey("salt", "HmacSHA1");
	Mac sig = Mac.getInstance("HmacSHA1");
        sig.init(salt);
        return sign.equals(byteArray2Hex(sig.doFinal(data.getBytes())));
    }

    private Session establishSession(String id) throws AxisFault {
	Session s = SessionManager.getSession(id);
	
	if (s == null)
	{
		throw new AxisFault("Session "+id+" is not active");
	}
	s.setActive();
	SessionManager.setCurrentSession(s);
	return s;
    }

    public String testsign(String data) throws AxisFault {
//	Session s = establishSession(sessionid);

	int i = data.indexOf("&time=");
	int j = data.indexOf("&", i+6);
	String time = data.substring(i+6,j);
	long mstime = java.lang.Long.parseLong(time);

	if (java.lang.Math.abs(mstime - System.currentTimeMillis()) > 30000)
	  return "stale value";

	return verifysign(data);
    }

    public String verifysign(String data) throws AxisFault {

	int i = data.lastIndexOf("&");
	String sign = data.substring(i+6);
	data = data.substring(0, i);

        boolean result = false;

	try {

        result = verify(data, sign);
//        System.out.println("Signature Verification Result = " + result);

	} catch (Exception e) {  
		return e.getClass().getName() + " : " + e.getMessage();
	}
	if (result)
 	 return "true";
        else
         return "false";

    }

    public String getsession(String object) throws AxisFault {
        return getsession(null, object);
    }

    public String getsession(String data, String object) throws AxisFault {
//	Session s = establishSession(sessionid);

        // Check user data

	if (data != null) {
          String sresult = verifysign(data);
          if (!sresult.equals("true"))
            throw new AxisFault("failed on user data");
        }


	// Check session object

	int i = object.lastIndexOf("&");
	String sign = object.substring(i+6);
	object = object.substring(0, i);

        boolean result = false;

	try {

        result = verify(object, sign);
//        System.out.println("Signature Verification Result = " + result);

	} catch (Exception e) {  
	    throw new AxisFault(e.getClass().getName() + " : " + e.getMessage());
	}
	if (!result)
          throw new AxisFault("failed on session object");

	if (object.equals("currentuser") && data != null)
	   return makesession(data, "internaluser=");
	else 
	   return makesession(object, "user=");

    }

    private String makesession(String spec, String attr) throws AxisFault {
	
	int i;
	if (!spec.startsWith(attr)) {
	    i = spec.indexOf("&"+ attr);
	    if (i > 0)
		spec = spec.substring(i+1);
	    else
		throw new AxisFault("unabled to find "+attr);
	}
	i = spec.indexOf("&");
	if (i > 0)
	    spec = spec.substring(0, i);
	i = spec.indexOf("=");
	// has to be there
	spec = spec.substring(i+1);
	    
	User user = null;
	try{
	    user = UserDirectoryService.getUser(spec);
	} catch (Exception e) {
	    throw new AxisFault(e.getClass().getName() + " : " + e.getMessage());
	}

	Session s = SessionManager.startSession();
	if (s == null)
	    throw new AxisFault("Unable to establish session");

	//	   System.out.println("creating session for " + user);
	s.setUserId(user.getId());
	s.setUserEid(user.getEid());

	return s.getId();
    }

    public String touchsession(String esessions) throws AxisFault {

// no security; you can't do anything damaging with this
// you just prevent a session from timing out

	int i;
	String esession;

	while (esessions != null) {
	    i = esessions.indexOf(",");
	    if (i > 0) {
		esession = esessions.substring(0, i);
		esessions = esessions.substring(i+1);
	    } else {
		esession = esessions;
		esessions = null;
	    }
	    esession = esession.trim();

	    Session s = null;
	    String sessionid = decrypt(esession);

	    System.out.println("session " + sessionid);

	    if (sessionid != null)
		s = SessionManager.getSession(sessionid);
	
	    if (s == null)
		throw new AxisFault("Session "+esession+" is not active");

	    s.setActive();
	}

	return "success";
    }

    private static String decrypt (String enc) {
	if (privateKey == null)
	   privateKey = readSecretKey("privkey", "Blowfish");

	try {
	  Cipher dcipher = Cipher.getInstance("Blowfish");
	  dcipher.init(Cipher.DECRYPT_MODE, privateKey);
	  byte[] dec = hex2byte(enc);
	  // Decrypt
          byte[] utf8 = dcipher.doFinal(dec);
          // Decode using utf-8
          return new String(utf8, "UTF8");
        } catch (Exception ignore) {
          System.out.println("SakaiSigning decrypt failed");
        }
	return null;
    }

    private static SecretKey readSecretKey(String name, String alg) {
	try {
	    String homedir = ServerConfigurationService.getSakaiHomePath();
	    if (homedir == null)
		homedir = "/etc/";
	    String filename = homedir + "sakai.rutgers.linktool." + name;
	    FileInputStream file = new FileInputStream(filename);
	    byte[] bytes = new byte[file.available()];
	    file.read(bytes);
	    file.close();
	    SecretKey privkey = new SecretKeySpec(bytes, alg);
            return privkey;
	} catch (Exception ignore) {
	    return null;
	}
    }

    private static char[] hexChars = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd'
, 'e', 'f'
    };

    /**
     * Convert byte array to hex string
     * 
     * @param ba
     *        array of bytes
     * @throws Exception.
     */

    private static String byteArray2Hex(byte[] ba){
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < ba.length; i++){
	    int hbits = (ba[i] & 0x000000f0) >> 4;
	    int lbits = ba[i] & 0x0000000f;
	    sb.append("" + hexChars[hbits] + hexChars[lbits]);
	}
	return sb.toString();
    }

    public static byte[] hex2byte(String strhex) {
	// System.out.println("hex2byte " + strhex); 

        if(strhex==null) return null;
        int l = strhex.length();
	
        if(l %2 ==1) return null;
        byte[] b = new byte[l/2];
        for(int i = 0 ; i < l/2 ;i++){
            b[i] = (byte)Integer.parseInt(strhex.substring(i *2,i*2 +2),16);
        }
        return b;
    }

}

