/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

/*
 * WebServiceSecurity.java
 *
 * Created on April 9, 2004, 2:28 PM
 */

package com.sun.enterprise.security.wss;

import java.util.HashMap;
import java.util.Set;

import com.sun.enterprise.security.jauth.*;
import com.sun.enterprise.security.ClientSecurityContext;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.audit.AuditManager;
import com.sun.enterprise.security.audit.AuditManagerFactory;
    
import java.security.Principal;
import javax.security.auth.Subject;
import javax.xml.soap.SOAPMessage;
import javax.servlet.http.HttpServletRequest;

import com.sun.logging.*;
import java.util.logging.*;

import com.sun.enterprise.webservice.WsUtil;
import com.sun.enterprise.webservice.monitoring.Endpoint;
import com.sun.enterprise.webservice.monitoring.EndpointType;
import com.sun.enterprise.webservice.monitoring.WebServiceEngineImpl;

/**
 *
 * Load Container auth spi.
 * @author  Harpreet Singh
 */

public class WebServiceSecurity {

    private static Logger _logger=null;
    static {
        _logger = LogDomains.getLogger(LogDomains.SECURITY_LOGGER);
    }

    private static AuditManager auditManager =
            AuditManagerFactory.getAuditManagerInstance();

    // keys to shared state (for things like session keys) in SOAPMessageCOntext
    private static final String SHARED_CLIENT_STATE = 
        "com.sun.enterprise.security.jauth.ClientHashMap";

    private static final String SHARED_SERVER_STATE = 
        "com.sun.enterprise.security.jauth.ServerHashMap";

    private WebServiceSecurity () {
    }
    
    // when called by jaxrpc SystemHandlerDelegate
    public static boolean 
	validateRequest(javax.xml.rpc.handler.soap.SOAPMessageContext context, 
			ServerAuthContext sAC)
        throws AuthException 
    {
	boolean rvalue = true;
	SOAPAuthParam param = 
	    new SOAPAuthParam(WsUtil.getMessage(context), null);

	// put sharedState in MessageContext for use by secureResponse
	HashMap sharedState = new HashMap();
	context.setProperty(SHARED_SERVER_STATE, sharedState);

	try {
	    rvalue = validateRequest(param, sharedState, sAC);
	} catch(PendingException pe){
            _logger.log(Level.FINE,
			"Container-auth: wss: Error validating request  ",pe);
	    context.setMessage(param.getResponse());
	    rvalue = false;
	} catch(FailureException fe){
            _logger.log(Level.FINE,
			"Container-auth: wss: Error validating request  ",fe);
	    context.setMessage(param.getResponse());
	    throw fe;
        }
	return rvalue;
    }

    // when called by jaxws SystemHandlerDelegate
    public static boolean 
	validateRequest(javax.xml.ws.handler.soap.SOAPMessageContext context,
			ServerAuthContext sAC)
        throws AuthException 
    {
	boolean rvalue = true;
	SOAPAuthParam param = 
	    new SOAPAuthParam(WsUtil.getMessage(context), null);

	// put sharedState in MessageContext for use by secureResponse
	HashMap sharedState = new HashMap();
	context.put(SHARED_SERVER_STATE, sharedState);

	try {
	    rvalue = validateRequest(param, sharedState, sAC);
	} catch(PendingException pe){
            _logger.log(Level.FINE,
			"Container-auth: wss: Error validating request  ",pe);
	    context.setMessage(param.getResponse());
	    rvalue = false;
	} catch(FailureException fe){
            _logger.log(Level.FINE,
			"Container-auth: wss: Error validating request  ",fe);
	    context.setMessage(param.getResponse());
	    throw fe;
        }
	return rvalue;
    }

    private static boolean 
	validateRequest(AuthParam param, HashMap sharedState, 
			ServerAuthContext sAC)
        throws AuthException 
    {
	boolean rvalue = true;

        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
            "Container Auth: ServerAuthContext.validateRequest");
        }
	
	Subject subject = null;
	boolean firstAuthentication = true;
	SecurityContext sc = SecurityContext.getCurrent();
	if (sc == null || sc.didServerGenerateCredentials()) {
	    subject = new Subject();
	} else {
	    subject = sc.getSubject();
	    firstAuthentication = false;
	}

	sAC.validateRequest((AuthParam)param, subject, sharedState);

	if (rvalue && firstAuthentication) {
	    Set principalSet = subject.getPrincipals();
	    // must be at least one new principal to establish
	    // non-default security contex
	    if (principalSet != null && !principalSet.isEmpty()) {
		// define and add initiator to Subject - note that this may add
		// a second principal (of type PrincipalImpl) for initiator.
		String initiator = ((Principal)principalSet.iterator().next()).
		    getName();
		SecurityContext newSC = new SecurityContext(initiator,subject);
		SecurityContext.setCurrent(newSC);
	    }
	}

        return rvalue;
    }
    
    // when called by jaxrpc SystemHandlerDelegate
    public static void 
	secureResponse(javax.xml.rpc.handler.soap.SOAPMessageContext context, 
		       ServerAuthContext sAC)
        throws AuthException 
    {
	secureResponse(WsUtil.getMessage(context),
		       (HashMap) context.getProperty(SHARED_SERVER_STATE),
		       sAC);
    }

    // when called by jaxws SystemHandlerDelegate
    public static void 
	secureResponse(javax.xml.ws.handler.soap.SOAPMessageContext context,			ServerAuthContext sAC)
        throws AuthException 
    {
	secureResponse(WsUtil.getMessage(context),
		       (HashMap) context.get(SHARED_SERVER_STATE), 
		       sAC);
    }

    private static void 
	secureResponse(SOAPMessage response, HashMap sharedState,
		       ServerAuthContext sAC)
        throws AuthException 
    {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
            "Container Auth: ServerAuthContext.secureResponse");
        }

	// subject may change if runAs identity differs from caller's.
	// Therefore, session state is saved in sharedState not subject
	SecurityContext sc = SecurityContext.getCurrent();
	Subject subject = sc.getSubject();

	SOAPAuthParam param = new SOAPAuthParam(null, response);

        try{
            sAC.secureResponse((AuthParam)param, subject, sharedState);
        } finally {
	    sAC.disposeSubject(subject,sharedState);
	}

        return;
    }

    // when called by jaxrpc Handler
    public static void 
	secureRequest(javax.xml.rpc.handler.soap.SOAPMessageContext context, 
		      ClientAuthContext cAC, boolean isAppClient)
        throws AuthException 
    {
	// put sharedState in MessageContext for use by validateResponse
	HashMap sharedState = new HashMap();
	context.setProperty(SHARED_CLIENT_STATE, sharedState);

	secureRequest
	    (WsUtil.getMessage(context), sharedState, cAC, isAppClient);
    }

    // when called by jaxws Handler or SystemHandlerDelegate
    public static void 
	secureRequest(javax.xml.ws.handler.soap.SOAPMessageContext context, 
		      ClientAuthContext cAC, boolean isAppClient)
        throws AuthException 
    {
	// put sharedState in MessageContext for use by validateResponse
	HashMap sharedState = new HashMap();
	context.put(SHARED_CLIENT_STATE, sharedState);

	secureRequest
	    (WsUtil.getMessage(context), sharedState, cAC, isAppClient);
    }

    private static void 
	secureRequest(SOAPMessage request, HashMap sharedState,
		      ClientAuthContext cAC, boolean isAppClient) 
        throws AuthException 
    {

        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
            "Container Auth: ClientAuthContext.secureRequest");
        }

	SOAPAuthParam param = new SOAPAuthParam(request, null);

	Subject subject = null;
	if (isAppClient) {
	    ClientSecurityContext sc = ClientSecurityContext.getCurrent();
	    if (sc != null) {
		subject = sc.getSubject();
	    }
	} else {
	    SecurityContext sc = SecurityContext.getCurrent();
	    if (sc != null && !sc.didServerGenerateCredentials()) {
		// make sure we don't use default unauthenticated subject, 
		// so that module cannot change this important (constant) 
		// subject.
		subject = sc.getSubject();
	    }
	}
	if (subject == null) subject = new Subject();
	
	cAC.secureRequest ( param, subject, sharedState);
    }
    
    // when called by jaxrpc Handler
    public static boolean 
	validateResponse(javax.xml.rpc.handler.soap.SOAPMessageContext context,
			 ClientAuthContext cAC)
        throws AuthException 
    {
	return validateResponse
	    (WsUtil.getMessage(context),
	     (HashMap) context.getProperty(SHARED_CLIENT_STATE), cAC);
    }

    // when called by jaxws Handler or SystemHandlerDelegate
    public static boolean 
	validateResponse(javax.xml.ws.handler.soap.SOAPMessageContext context, 
			 ClientAuthContext cAC)
        throws AuthException 
    {
	return validateResponse
	    (WsUtil.getMessage(context),
	     (HashMap) context.get(SHARED_CLIENT_STATE), cAC);
    }

    private static boolean 
	validateResponse(SOAPMessage response, HashMap sharedState, 
			 ClientAuthContext cAC) 
        throws AuthException 
    {
        boolean rvalue = true;

	// get a subject to be filled in with the principals of the responder
	Subject responderSubject = new Subject();

	SOAPAuthParam param = new SOAPAuthParam(null, response);

        try{
            cAC.validateResponse( param, responderSubject, sharedState);
        } catch(AuthException ae){
            _logger.log(Level.SEVERE,
			"Container-auth: wss: Error validating response ", ae);
	    rvalue = false;
            throw ae;
        } finally {
	    cAC.disposeSubject(responderSubject,sharedState);
	}
        
        return rvalue;
    }

    // when called by jaxrpc SystemHandlerDelegate
    public static void auditInvocation
    (javax.xml.rpc.handler.soap.SOAPMessageContext context, boolean status) {

	if (auditManager.isAuditOn()) {

	    // TODO: replace the string literal with the correct constant
	    // MessageContextProperties.HTTP_SERVLET_REQUEST);

	    HttpServletRequest req = (HttpServletRequest)context.getProperty
		("com.sun.xml.rpc.server.http.HttpServletRequest");
       
	    String uri = null;

	    if( req != null ) {
		uri = req.getRequestURI();
	    }
	    
	    Endpoint endpoint = WebServiceEngineImpl.getInstance().getEndpoint
		(req.getRequestURL().toString());

	    String epName = null;

	    if( endpoint != null ) {
		epName = endpoint.getDescriptor().getEndpointName();
	    }

	    auditManager.webServiceInvocation
		( ((uri==null) ? "(no uri)" : uri), 
		  ((epName==null) ? "(no endpoint)" : epName), 
		  status);
	}
    }

    // when called by jaxws SystemHandlerDelegate
    public static void auditInvocation
    (javax.xml.ws.handler.soap.SOAPMessageContext context, boolean status) {

	if (auditManager.isAuditOn()) {

	    HttpServletRequest req = (HttpServletRequest)context.get
		(javax.xml.ws.handler.MessageContext.SERVLET_REQUEST);
       
	    String uri = null;

	    if( req != null ) {
		uri = req.getRequestURI();
	    }
	    
	    String epName = null;

	    Endpoint endpoint = WebServiceEngineImpl.getInstance().getEndpoint
		(req.getRequestURL().toString());
	    
	    if( endpoint != null ) {
		epName = endpoint.getDescriptor().getEndpointName();
	    }

	    auditManager.webServiceInvocation
		( ((uri==null) ? "(no uri)" : uri), 
		  ((epName==null) ? "(no endpoint)" : epName), 
		  status);
	}
    }

}
