/** 
 * 
 * Copyright 2004 Hiram Chirino
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 * 
 **/
package org.activemq.security.jassjacc;

import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.Iterator;

import javax.jms.JMSException;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyConfigurationFactory;
import javax.security.jacc.PolicyContext;
import javax.security.jacc.PolicyContextException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.broker.BrokerClient;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ConnectionInfo;
import org.activemq.message.ConsumerInfo;
import org.activemq.message.ProducerInfo;
import org.activemq.security.SecurityAdapter;

/**
 * Implements SecurityAdapter that uses JASS to authenticate and
 * JACC to authorize the user operations.
 * 
 * @version $Revision: 1.1.1.1 $
 */
public class JassJaccSecurityAdapter implements SecurityAdapter {
	
	static final private Log log = LogFactory.getLog(JassJaccSecurityAdapter.class);	
	private String jassConfiguration;

	/**
	 * @param jassConfiguration
	 */
	public JassJaccSecurityAdapter(String jassConfiguration) {
		this.jassConfiguration = jassConfiguration;
	}
	
	
	protected AccessControlContext getAccessControlContext(BrokerClient client) {
		
		Subject subject = client.getSubject();
        if (subject == null) throw new IllegalArgumentException("Subject must not be null");

        AccessControlContext acc = (AccessControlContext) Subject.doAsPrivileged(subject, new PrivilegedAction() {
            public Object run() {
                return AccessController.getContext();
            }
        }, null);        
        
		return acc;
	}
	
	static protected String getBrokerName(BrokerClient client) {
		return client.getBrokerConnector().getBrokerInfo().getBrokerName();
	}

	public void authorizeConnection(BrokerClient client, ConnectionInfo info) throws JMSException {

		// Set the TCCL since it seems JAAS needs it to find the login module classes.
		Thread.currentThread().setContextClassLoader(JassJaccSecurityAdapter.class.getClassLoader());
		
		// Do the login.
        Subject subject = doJassLogin(info);
        client.setSubject(subject);        
		
        // Can the user really use the broker?
		PolicyContext.setContextID(getBrokerPoicyContextId(client));
        AccessControlContext accessContext = getAccessControlContext(client);
        if (accessContext != null) {
            Permission permission = new JMSBrokerPermission(getBrokerName(client),JMSBrokerPermission.CONNECT_ACTION);
            accessContext.checkPermission(permission);
        }
    }

	/**
	 * @param client
	 * @return
	 */
	static private String getBrokerPoicyContextId(BrokerClient client) {
		return getBrokerPolicyContextId(getBrokerName(client));
	}

	/**
	 * @param client
	 * @return
	 */
	static private String getBrokerPolicyContextId(String brokerName) {
		return "org.activemq.broker:"+brokerName;
	}

	/**
	 * @param info
	 * @param subject
	 * @return
	 */
	private Subject doJassLogin(ConnectionInfo info) throws JMSException {
		try {
			LoginContext lc = new LoginContext(jassConfiguration,
					new UsernamePasswordCallback(info.getUserName(), info
							.getPassword()));
			lc.login();
			return lc.getSubject();
		} catch (Exception e) {
			throw (JMSException)new JMSException("Login failed: "+e.getMessage()).initCause(e);
		}
	}

	public void authorizeConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
    	PolicyContext.setContextID(getDestinationPoicyContextId(client, info.getDestination()));
        AccessControlContext accessContext = getAccessControlContext(client);
        if (accessContext != null) {
            Permission permission = new JMSDestinationPermission(info.getDestination().getPhysicalName(),JMSDestinationPermission.CONSUME_ACTION);
            accessContext.checkPermission(permission);
        }
    }


	/**
	 * @param client
	 * @param destination
	 * @return
	 */
	static private String getDestinationPoicyContextId(BrokerClient client, ActiveMQDestination destination) {
		return getDestinationPoicyContextId(getBrokerName(client), destination);
	}
	
	static private String getDestinationPoicyContextId(String brokerName, ActiveMQDestination destination) {
		ActiveMQDestination activeMQDestination = ((ActiveMQDestination)destination);
		return (activeMQDestination.isTopic()?"org.activemq.topic:":"org.activemq.queue:")+brokerName+":"+activeMQDestination.getPhysicalName();
	}

	public void authorizeProducer(BrokerClient client, ProducerInfo info) throws JMSException {
    	PolicyContext.setContextID(getDestinationPoicyContextId(client, info.getDestination()));
        AccessControlContext accessContext = getAccessControlContext(client);
        if (accessContext != null) {
            Permission permission = new JMSDestinationPermission(info.getDestination().getPhysicalName(),JMSDestinationPermission.PRODUCE_ACTION);
            accessContext.checkPermission(permission);
        }
    }

	public void authorizeSendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
    	PolicyContext.setContextID(getDestinationPoicyContextId(client, message.getJMSActiveMQDestination()));
        AccessControlContext accessContext = getAccessControlContext(client);
        if (accessContext != null) {
            String physicalName = ((ActiveMQDestination)message.getJMSDestination()).getPhysicalName();
			Permission permission = new JMSDestinationPermission(message.getJMSActiveMQDestination().getPhysicalName(),JMSDestinationPermission.SEND_ACTION);
            accessContext.checkPermission(permission);
        }
    }
		
	/**
	 * Creates a JACC PolicyConfiguration for the broker security.
	 * 
	 * @param brokerSecurity
	 */
	static public void secure(BrokerSecurityConfig brokerSecurity) {

        try {
        	
			PolicyConfigurationFactory factory = PolicyConfigurationFactory.getPolicyConfigurationFactory();
			PolicyConfiguration policyConfiguration = factory.getPolicyConfiguration(
					getBrokerPolicyContextId(brokerSecurity.getBrokerName()), true);

			for (Iterator iter = brokerSecurity.getConnectRoles().iterator(); iter.hasNext();) {
				String role = (String) iter.next();
				policyConfiguration.addToRole(role, new JMSBrokerPermission(
						brokerSecurity.getBrokerName(), JMSBrokerPermission.CONNECT_ACTION));
			}

			policyConfiguration.commit();

        } catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (PolicyContextException e) {
			e.printStackTrace();
		}
		
	}

	/**
	 * Creates a JACC PolicyConfiguration for the broker security.
	 * 
	 * @param brokerSecurity
	 */
	static public void secure(DestinationSecurityConfig destinationSecurity) {

        try {
        	
        	String policyId = getDestinationPoicyContextId(destinationSecurity.getBrokerName(), destinationSecurity.getDestination());
			PolicyConfigurationFactory factory = PolicyConfigurationFactory.getPolicyConfigurationFactory();
			PolicyConfiguration policyConfiguration = factory.getPolicyConfiguration(policyId, true);

			for (Iterator iter = destinationSecurity.getConsumeRoles().iterator(); iter.hasNext();) {
				String role = (String) iter.next();
				policyConfiguration.addToRole(role, new JMSDestinationPermission(
						destinationSecurity.getDestination().getPhysicalName(), JMSDestinationPermission.CONSUME_ACTION));
			}
			for (Iterator iter = destinationSecurity.getProduceRoles().iterator(); iter.hasNext();) {
				String role = (String) iter.next();
				policyConfiguration.addToRole(role, new JMSDestinationPermission(
						destinationSecurity.getDestination().getPhysicalName(), JMSDestinationPermission.PRODUCE_ACTION));
			}
			for (Iterator iter = destinationSecurity.getSendRoles().iterator(); iter.hasNext();) {
				String role = (String) iter.next();
				policyConfiguration.addToRole(role, new JMSDestinationPermission(
						destinationSecurity.getDestination().getPhysicalName(), JMSDestinationPermission.SEND_ACTION));
			}
			
			policyConfiguration.commit();

        } catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (PolicyContextException e) {
			e.printStackTrace();
		}
		
	}

}
