/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.server.core.comm;

import java.util.Hashtable;
import java.util.Map;
import org.rhq.core.clientapi.server.core.CoreServerService;
import org.rhq.core.domain.resource.Agent;
import org.rhq.enterprise.communications.command.Command;
import org.rhq.enterprise.communications.command.impl.remotepojo.RemotePojoInvocationCommand;
import org.rhq.enterprise.communications.command.server.CommandAuthenticator;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;

/**
 * This command authenticator implementation is designed to be installed on the JON Server so it can authenticate JON
 * Agents that are sending commands. It performs the necessary checks on the security token found within the command in
 * order to ensure the authenticity of the agent sending the command.
 *
 * @author John Mazzitelli
 */
public class SecurityTokenCommandAuthenticator implements CommandAuthenticator {
    /**
     * This is the name of the command configuration property whose value (if it exists) is the security token string.
     * Each incoming command should contain a security token within the command's
     * {@link Command#getConfiguration() configuration} unless it is a command that is designed to initially establish a
     * security token.
     */
    private static final String CMDCONFIG_PROP_SECURITY_TOKEN = "rhq.security-token";

    /**
     * A cache keyed on security token strings whose values are the timestamps when the token entries were stored in the
     * cache.
     */
    private static final Map<String, Long> TOKENS_CACHE = new Hashtable<String, Long>();

    /**
     * Items in the tokens cache will be valid for this amount of milliseconds. After an token grows this old in the
     * cache, that token's authenticity will again be checked with the database.
     */
    private static final long TOKENS_CACHE_TIMEOUT = 5 * 60 * 1000L;

    /**
     * This is the server service interface that contains the "register agent" API.
     */
    private static final String REGISTER_SERVICE_INTERFACE = CoreServerService.class.getName();

    /**
     * This is the name of the command type that will house all "register agent" commands.
     */
    private static final String REGISTER_COMMAND_TYPE_NAME = RemotePojoInvocationCommand.COMMAND_TYPE.getName();

    /**
     * EJB that is to be used to authenticate security tokens.
     */
    private AgentManagerLocal m_agentManager;

    /**
     * @see CommandAuthenticator#isAuthenticated(Command)
     */
    public boolean isAuthenticated(Command command) {
        String security_token = command.getConfiguration().getProperty(CMDCONFIG_PROP_SECURITY_TOKEN);

        // if no security token is in the command, reject it unless this command is asking for a token.
        // if registering - allow it to be considered authenticated since its asking to be registered for the first time
        if (security_token == null) {
            return isRegisterCommand(command);
        }

        // check the validity of the security token
        long now = System.currentTimeMillis();
        Long timestamp = TOKENS_CACHE.get(security_token);

        if ((timestamp == null) || ((timestamp.longValue() + TOKENS_CACHE_TIMEOUT) < now)) {
            try {
                AgentManagerLocal agentManager = getAgentManager();

                // see if the agent token is valid - note that we do not know if the token is coming
                // from the particular agent it is assigned to - for true security, SSL certs should be used
                Agent agent = agentManager.getAgentByAgentToken(security_token);
                if (agent == null) {
                    throw new NullPointerException();
                }

                TOKENS_CACHE.put(security_token, new Long(now));

                // let's take the opportunity to tell the server that this agent is up!
                agentManager.agentIsAlive(agent);
            } catch (Exception e) {
                TOKENS_CACHE.remove(security_token);
                return false;
            }
        }

        return true;
    }

    /**
     * This examines the given command and determines if it is a command asking to register an agent.
     *
     * @param  command
     *
     * @return <code>true</code> if the given command is asking to register an agent; any other command results in this
     *         method returning <code>false</code>
     */
    private boolean isRegisterCommand(Command command) {
        if (REGISTER_COMMAND_TYPE_NAME.equals(command.getCommandType().getName())) {
            RemotePojoInvocationCommand remote_cmd = (RemotePojoInvocationCommand) command;
            String iface_name = remote_cmd.getTargetInterfaceName();

            if (REGISTER_SERVICE_INTERFACE.equals(iface_name)) {
                String method_name = remote_cmd.getNameBasedInvocation().getMethodName();

                if ("registerAgent".equals(method_name)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns the EJB used to authenticate security tokens.
     *
     * @return EJB used to authenticate tokens
     *
     * @throws Exception
     */
    private AgentManagerLocal getAgentManager() throws Exception {
        if (m_agentManager == null) {
            m_agentManager = LookupUtil.getAgentManager();
        }

        return m_agentManager;
    }
}