/*
 * Created on 28-Feb-2005
 *
 * ShibbolethBrowserSession v1.0
 * Member package of: SoapShibboleth - v1.0
 * 
 * A package of web services which allows any application to validate a user using a Shibboleth setup.
 * It can be used to find out per-resource whether a user has access.  Furthermore, it allows the application
 * to get hold of the specific attributes held about a user for a particular resource.
 * 
 * The set of classes in the package shibbolethBrowserSession.shibbolethBrowserSessionWsdl (ShibbolethBrowserSession)
 * handle the creation of a ShibbolethBrowserSession and allow instances of it to be created, destroyed, inspected
 * and also allow particular methods to be called on that instance (resource).
 */

package shibbolethBrowserSession.shibbolethBrowserSessionWsdl;

import org.apache.ws.resource.ResourceContextException;
import org.apache.ws.resource.ResourceException;
import org.apache.ws.resource.ResourceContext;

import org.oasisOpen.docs.wsrf.x2004.x11.wsrfWSResourceProperties12Draft05.GetResourcePropertyDocumentDocument1;
import org.oasisOpen.docs.wsrf.x2004.x11.wsrfWSResourceProperties12Draft05.GetResourcePropertyDocumentResponseDocument;
import org.apache.ws.resource.properties.v1_2_draft05.impl.NamespaceVersionHolderImpl;


import org.apache.commons.httpclient.HttpClient;
import org.apache.log4j.Logger;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.*;

import sun.misc.BASE64Encoder;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.ws.resource.lifetime.v1_2_draft04.porttype.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;


import shibbolethBrowserSession.shibbolethBrowserSessionWsdl.common.*;
import javax.swing.text.html.*;
import javax.swing.text.html.parser.*;
import java.io.StringReader;
import shibbolethBrowserSession.shibbolethBrowserSession.*;


/**
 * This class should be generated ONCE (and not overwritten) to maintain user-added code.
 * If there is a change to the WSDL, then the generated implemented interfaces
 * (representing the "base" portTypes) will change, thus showing a compile error to the
 * user.
 *
 * NOTE: This class is generated. However, it will not be overwritten by subsequent
 *       calls to the code generator.
 *
 */
public class ShibbolethBrowserSessionService
extends AbstractShibbolethBrowserSessionService 
implements ShibbolethBrowserSessionCustomOperationsPortType,ImmediateResourceTerminationPortType
{
	
	private ResourceContext m_resourceContext;
	private static Logger log = Logger.getLogger(ShibbolethBrowserSessionService.class.getName());
	/**
	 * Creates a new {@link ShibbolethBrowserSessionService } object.
	 *
	 * @param resourceContext DOCUMENT_ME
	 */
	public ShibbolethBrowserSessionService( ResourceContext resourceContext )
	{
		m_resourceContext = resourceContext;
		init();
	}
	
	/**
	 * DOCUMENT_ME
	 *
	 * @return DOCUMENT_ME
	 */
	public ResourceContext getResourceContext(  )
	{
		return m_resourceContext;
	}
	
	public shibbolethBrowserSession.shibbolethBrowserSession.GetUserAttributesResponseDocument GetUserAttributes( shibbolethBrowserSession.shibbolethBrowserSession.GetUserAttributesRequestDocument requestDoc )
	{   
		
		//todo implement  
		return null;
	}
	
	/**
	 * This method HTTP POSTS the supplied SAMLResponse/TARGET in the requestdocument to the SHIRE
	 * and returns a response with comment OK if there is no problem. Otherwise a custom soap fault is returned
	 * containing the error message created by Shibboleth.
	 * 
	 * @param VerifySAMLRequestDocument
	 * @return VerifySAMLResponseDocument
	 */
	public shibbolethBrowserSession.shibbolethBrowserSession.VerifySAMLResponseDocument VerifySAML( shibbolethBrowserSession.shibbolethBrowserSession.VerifySAMLRequestDocument requestDoc )
	{ 
		log.info("Starting to execute VerifySAML method...");
		VerifySAMLResponseDocument responseDocument = VerifySAMLResponseDocument.Factory.newInstance();
		
		try
		{
			//   get the HttpClient resource belonging to the specified instance
			ShibbolethBrowserSessionResource resource = (ShibbolethBrowserSessionResource) getResourceContext().getResource();
			HttpClient httpClient =  resource.getHttpClient();
			log.info("Retrieved HTTPClient from ShibbolethBrowserSessionResource...");
			
			//make sure the user is logged into the HS - cannot proceed otherwise
			if (resource.getSHIBBOLETHBROWSERSESSION_STATE()==ShibbolethBrowserSessionStateType.HS_LOGGED_IN)
			{	
				String SAMLResponse = requestDoc.getVerifySAMLRequest().getSAMLResponse();
				String TARGET = requestDoc.getVerifySAMLRequest().getTARGET();
				
				//make sure they arent empty
				if (SAMLResponse==null || TARGET==null || SAMLResponse.equals("") || TARGET.equals(""))
				{
					log.error("Empty SAMLResponse or Target supplied.");
					throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "Empty SAMLResponse or Target supplied.Cannot Proceed!");
				}
				
				//try and POST them to the SHIRE
				PostMethod post = new PostMethod(resource.getSHIRE_URL().toString());
				post.setFollowRedirects(false);
				post.addParameter("TARGET",TARGET);
				
				post.addParameter("SAMLResponse",SAMLResponse);
				
				int statusCode = httpClient.executeMethod( post );
				
				//a redirect (HTTP 302) is generated by shibb upon recieving a SAMLResponse - or an OK if an error page will load
				if( statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_OK) {
					String sHIREResponse = post.getResponseBodyAsString();
					// log.info("SHIRE Response from SAMLResponse verification:" + sHIREResponse);
					post.releaseConnection();
					
					//if text "302 Found" then the SAMLResponse check was a success as it wants to
					//redirect the user to the final resource.
					//if not, an error has been produced, and it should be returned as a SOAPFault
					//back to the user
					
					if (sHIREResponse.split("302 Found").length==2) //if it can split it, it MUST contain "302 Found"
					{
						log.info("SHIRE has successfully accepted the SAMLResponse");
						
						//update the resource's shibboleth state
						resource.setSHIBBOLETHBROWSERSESSION_STATE(ShibbolethBrowserSessionStateType.HS_SAML_RESPONSE_VERIFIED);
						
						responseDocument.addNewVerifySAMLResponse();
						responseDocument.getVerifySAMLResponse().setComment("OK");
					}
					else
					{
						log.error("SHIRE has NOT successfully accepted the SAMLResponse..attempting to extract error message...");
						
						//must have had an error. Extract the raw text from the HTML and return the string.
						HTMLEditorKit.Parser parser = new ParserDelegator();
						StringReader sHIREResponseReader = new StringReader(new String(sHIREResponse));
						//parse the HTML - we want the two tags for SAMLResponse and TARGET
						SHIREHTMLParserForSAMLResponseVerification sHIREHTMLParserForSAMLResponseVerification= new SHIREHTMLParserForSAMLResponseVerification();
						parser.parse(sHIREResponseReader,sHIREHTMLParserForSAMLResponseVerification, true);
						
						//print just the error message from the SHIRE
						log.info("Returning error message as fault: " + sHIREHTMLParserForSAMLResponseVerification.plainText);
						throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(),"[SHIRE ERROR MESSAGE] " + sHIREHTMLParserForSAMLResponseVerification.plainText);   
					}
				}
				else
				{
					log.error("Failed to POST the SAMLResponse to the SHIRE");
					throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "Failed to HTTP POST the SAMLResponse to the SHIRE successfully - " + post.getStatusLine());
				}
				
			}
			else //not logged in
			{
				log.error("User is not logged into the HS first.");
				throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "Not already logged into the HS. Log into the HS first by calling the HSLogin method!");
			}
		}
		catch (ResourceContextException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");
		}
		catch (ResourceException re)
		{
			re.printStackTrace();
			throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");     
		}  
		catch (HttpException e) {
			//e.printStackTrace();
			throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "HttpException: Problem connecting to the SHIRE to verify the SAMLResponse..." + e.getMessage()); 
		} catch (IOException e) {
			//e.printStackTrace();
			throw new SAMLResponseInvalidFaultException(new NamespaceVersionHolderImpl(), "IOException: Problem connecting to the SHIRE to verify the SAMLResponse..." + e.getMessage()); 
		} 
	
		return responseDocument;
	}
	
	/**
	 * This public method available through the WSRF actually logs the user into the HS,
	 * it returns a SOAP message containing the target URL and SAMLResponse as found in the
	 * HTML which the HS produces after a successful login.
	 * 
	 * @param requestDoc
	 * @return responseDocument
	 */
	public shibbolethBrowserSession.shibbolethBrowserSession.HSLoginResponseDocument HSLogin( shibbolethBrowserSession.shibbolethBrowserSession.HSLoginRequestDocument requestDoc )
	{ 
		log.info("Starting to execute HSLogin method...");
		HSLoginResponseDocument responseDocument=  HSLoginResponseDocument.Factory.newInstance();
		try
		{
			//get the HttpClient resource belonging to the specified instance
			ShibbolethBrowserSessionResource resource = (ShibbolethBrowserSessionResource) getResourceContext().getResource();
			HttpClient httpClient =  resource.getHttpClient();
			log.info("Retrieved HTTPClient from ShibbolethBrowserSessionResource...");
			
			//now set each of the resource properties
			resource.setHS_URL(new URL(requestDoc.getHSLoginRequest().getHSURL()));
			resource.setSHIRE_URL(new URL(requestDoc.getHSLoginRequest().getSHIREURL()));
			resource.setTARGET_URL(new URL(requestDoc.getHSLoginRequest().getTargetURL()));
			
			//set it to expect to enter a username password
			httpClient.getParams().setAuthenticationPreemptive(true);
			
			String username = requestDoc.getHSLoginRequest().getUsername();
			String password = requestDoc.getHSLoginRequest().getPassword();
			
			//if either are null (undefined), set them to be empty
			if (username==null)
				username="";
			
			if (password==null)
				password="";
			
			
			Credentials defaultcreds = new UsernamePasswordCredentials(requestDoc.getHSLoginRequest().getUsername(),requestDoc.getHSLoginRequest().getPassword());
			httpClient.getState().setCredentials(null, null, defaultcreds);
			
			String actualURLToGoTo;
			actualURLToGoTo = resource.getHS_URL() + "?" + "target=" + resource.getTARGET_URL() + "&shire=" + resource.getSHIRE_URL();
			log.info("Attempting to connect to HS with URL: " + actualURLToGoTo);
			
			// Create a method instance.
			GetMethod method = new GetMethod(actualURLToGoTo);
			method.setDoAuthentication(true);
			
			// Execute the method.
			int statusCode = httpClient.executeMethod(method);
			
			if (statusCode != HttpStatus.SC_OK) {
				log.info("HTTP_OK Not returned following HTTP HS Connection: "  + method.getStatusLine());
				
				//password must be incorect
				if (statusCode == HttpStatus.SC_UNAUTHORIZED)
				{
					resource.setSHIBBOLETHBROWSERSESSION_STATE(ShibbolethBrowserSessionStateType.NOT_LOGGED_IN);
					throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Couldn't log into HS - INCORRECT USERNAME/PASSWORD:" + method.getStatusLine());     
				}
				else
				{
					resource.setSHIBBOLETHBROWSERSESSION_STATE(ShibbolethBrowserSessionStateType.NOT_LOGGED_IN);
					throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Problem logging into HS:" + method.getStatusLine());     
					
				}
			}
			
			//they are logged in, so update their resource setting for SHIBBOLETHBROWSERSESSION_STATE
			resource.setSHIBBOLETHBROWSERSESSION_STATE(ShibbolethBrowserSessionStateType.HS_LOGGED_IN);
			
			// Read the response body - from here on, we must have logged into HS correctly
			byte[] responseBody = method.getResponseBody();
			StringReader htmlReader = new StringReader(new String(responseBody));
			HTMLEditorKit.Parser parser = new ParserDelegator();
			
			//parse the HTML - we want the two tags for SAMLResponse and TARGET
			
			HSHTMLParser hsParser= new HSHTMLParser();
			parser.parse(htmlReader,hsParser, true);
			
			// if an empty SAML Response is returned throw error
			if (hsParser.SAMLResponse.trim().equals(""))
			{
				log.error("Empty SAML (base64 encoded) returned by HTML HS Parser.");
				throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Problem extracting the SAMLResponse from the HS output.");     
			}
			
			//construct a response document to return
			responseDocument.addNewHSLoginResponse();
			responseDocument.getHSLoginResponse().setSAMLResponse(hsParser.SAMLResponse.trim());
			responseDocument.getHSLoginResponse().setTARGET(hsParser.targetURL);
			
			//save the user handle into the resource as its a resource property - makes it available for inspection
			resource.setUSERHANDLEID(ShibbolethBrowserSessionCommonMethods.getUserHandleFromSAMLResponseFromHS(hsParser.SAMLResponse));
			
		}
		catch (ResourceContextException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");
		}
		catch (ResourceException re)
		{
			re.printStackTrace();
			throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");     
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			log.error("Invalid URL: "  + e.getMessage());
			//e.printStackTrace();
			throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "Invalid URL Supplied - ensure the supplied HS,SHIRE,TARGET,SHIRE URL are legitimate!");     
		} 
		catch (HttpException e) {
			//e.printStackTrace();
			throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "HttpException: Problem connecting to the HS..." + e.getMessage()); 
		} catch (IOException e) {
			//e.printStackTrace();
			throw new LoginFailedFaultException(new NamespaceVersionHolderImpl(), "IOException: Problem connecting to the HS..." + e.getMessage()); 
		}
		
		
		return responseDocument;
	}
	
	
	/**
	 * This method actually tries to connect to the intended resource. It assumes the user has
	 * already logged into the HS and that the SAMLResponse has been verified.
	 * It returns a AccessShibbolizedResourceResponse containing the HTML responsecodes and actual HTML returned.
	 * 
	 * @param AccessShibbolizedResourceRequestDocument
	 * @return AccessShibbolizedResourceResponseDocument
	 */
	public shibbolethBrowserSession.shibbolethBrowserSession.AccessShibbolizedResourceResponseDocument AccessShibbolizedResource( shibbolethBrowserSession.shibbolethBrowserSession.AccessShibbolizedResourceRequestDocument requestDoc )
	{              
		log.info("Starting to execute AccessShibbolizedResource method...");
		AccessShibbolizedResourceResponseDocument responseDocument=  AccessShibbolizedResourceResponseDocument.Factory.newInstance();
		try
		{
			//   	get the HttpClient resource belonging to the specified instance
			ShibbolethBrowserSessionResource resource = (ShibbolethBrowserSessionResource) getResourceContext().getResource();
			HttpClient httpClient =  resource.getHttpClient();
			log.info("Retrieved HTTPClient from ShibbolethBrowserSessionResource...");
			
			//the SAMLResponse from the HS must be successfully verified to proceed
			if (resource.getSHIBBOLETHBROWSERSESSION_STATE()==ShibbolethBrowserSessionStateType.HS_SAML_RESPONSE_VERIFIED)
			{
				
				GetMethod method = new GetMethod(resource.getTARGET_URL().toString());
				int statusCode = httpClient.executeMethod( method );
				
				//as long as it performed the HTTP GET successfully
				if( statusCode == HttpStatus.SC_FORBIDDEN ||  statusCode == HttpStatus.SC_UNAUTHORIZED) 
				{
					log.info("ACCESS DENIED to Shibboleth Target - " + method.getStatusLine());
					throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "Access denied to Shibboleth Target - " + method.getStatusLine());     
				}
				else 
				{
					//as long as it managed to do the HTTP GET successfully
					if (statusCode!=-1)
					{
						BASE64Encoder encoder = new BASE64Encoder();
						String contents = encoder.encode(method.getResponseBody());
						responseDocument.addNewAccessShibbolizedResourceResponse();
						responseDocument.getAccessShibbolizedResourceResponse().setHTMLRETURNED(contents);
						responseDocument.getAccessShibbolizedResourceResponse().setHTTPRESPONSECODE(String.valueOf(statusCode));
						responseDocument.getAccessShibbolizedResourceResponse().setHTTPRESPONSEMESSAGE(method.getStatusLine().toString());
						method.releaseConnection();
						
						//they are logged in to the resource, so update their resource setting for SHIBBOLETHBROWSERSESSION_STATE
						resource.setSHIBBOLETHBROWSERSESSION_STATE(ShibbolethBrowserSessionStateType.RESOURCE_LOGGED_IN);
						
					}
					else
					{
						//there must have been a fundamental problem in performing the HTTPGET
						log.error("Major error when trying to do HTTP GET to Shibboleth TARGET");
						throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "Major unknown error when trying to do HTTP GET to Shibboleth TARGET....");     
					}
				}
				
			}
			else
			{
				log.error("VerifySAML not called first! AccessShibbolizedResource cannot continue to try and access the target!");
				throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "SAMLResponse from HS needs to be verified before attempting to access the Shibboleth Target...call VerifySAML first!");	
			}
			
			
		}
		catch (ResourceContextException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");
		}
		catch (ResourceException re)
		{
			re.printStackTrace();
			throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "Problem locating resource.");     
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			log.error("Invalid URL: "  + e.getMessage());
			//e.printStackTrace();
			throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "Invalid URL Supplied - ensure the supplied HS,SHIRE,TARGET,SHIRE URL are legitimate!");     
		} 
		catch (HttpException e) {
			//e.printStackTrace();
			throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "HttpException: Problem connecting to the Shibboleth Resource..." + e.getMessage()); 
		} catch (IOException e) {
			//e.printStackTrace();
			throw new ShibbolizedResourceAccessDeniedFaultException(new NamespaceVersionHolderImpl(), "IOException: Problem connecting to the Shibboleth Resource..." + e.getMessage()); 
		} 
		
		return responseDocument;
	}
	
	/* (non-Javadoc)
	 * @see org.apache.ws.resource.properties.v1_2_draft05.porttype.GetResourcePropertyDocumentPortType#getResourcePropertyDocument(org.oasisOpen.docs.wsrf.x2004.x11.wsrfWSResourceProperties12Draft05.GetResourcePropertyDocumentDocument1)
	 */
	public GetResourcePropertyDocumentResponseDocument getResourcePropertyDocument(GetResourcePropertyDocumentDocument1 arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	
	public org.oasisOpen.docs.wsrf.x2004.x11.wsrfWSResourceLifetime12Draft04.DestroyResponseDocument destroy( org.oasisOpen.docs.wsrf.x2004.x11.wsrfWSResourceLifetime12Draft04.DestroyDocument requestDoc )
	{
		return new org.apache.ws.resource.lifetime.v1_2_draft04.porttype.impl.ImmediateResourceTerminationPortTypeImpl( getResourceContext(  ) ).destroy( requestDoc );
	}
	
	
	
	
	
}

