/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.rampart.policy.model;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.neethi.Assertion;
import org.apache.neethi.Constants;
import org.apache.neethi.PolicyComponent;
import org.apache.rampart.RampartException;
import org.ietf.jgss.GSSCredential;

/**
 * Container for Kerberos configuration options.
 */
public class KerberosConfig implements Assertion {

    public final static String KERBEROS_LN = RampartConfig.KERBEROS_CONFIG;

    public final static String JAAS_CONTEXT_LN = "jaasContext";

    public final static String PRINCIPAL_NAME_LN = "principalName";

    public final static String PRINCIPAL_PASSWORD_LN = "principalPassword";

    public final static String SERVICE_PRINCIPAL_NAME_LN = "servicePrincipalName";

    public final static String SERVICE_PRINCIPAL_NAME_FORM_LN = "servicePrincipalNameForm";

    public final static String KERBEROS_TOKEN_DECODER_CLASS_LN = "kerberosTokenDecoderClass";
    
    public final static String REQUEST_CREDENTIAL_DELEGATION_LN  = "requestCredentialDelegation";
    
    public final static String DELEGATION_CREDENTIAL_LN = "delegationCredential";

    /**
     * Specifies that the service principal name should be interpreted as a 
     * "host-based" name as specified in GSS API RFC, 
     * section "4.1: Host-Based Service Name Form". 
     * See <a href="http://www.ietf.org/rfc/rfc2743.txt">rfc2743 - GSS
     * API, Version 2</a>.
     */
    public final static String HOST_BASED_NAME_FORM = "hostbased";

    /**
     * Specifies that the service principal name should be interpreted as a   
     * "username" name as specified in GSS API RFC,
     * section "4.2: User Name Form". 
     * See <a href="http://www.ietf.org/rfc/rfc2743.txt">rfc2743 - GSS API, Version
     * 2</a>.
     */
    public final static String USERNAME_NAME_FORM = "username";

    private String jaasContext;

    private String principalName;

    private String principalPassword;

    private String servicePrincipalName;

    private String servicePrincipalNameForm;

    private String kerberosTokenDecoderClass;
    
    private boolean requstCredentialDelegation;
    
    private GSSCredential delegationCredential;
    
    /**
     * @return The JAAS context name to use to obtain a TGT (Ticket granting ticket).
     */
    public String getJaasContext() {
        return jaasContext;
    }

    /**
     * Sets the JAAS context name to use to obtain a TGT (Ticket granting ticket).
     * @param jaasContext the jaasContext to set
     */
    public void setJaasContext(String jaasContext) {
        this.jaasContext = jaasContext;
    }

    /**
     * @return The principal name to use to obtain a TGT (Ticket granting ticket).
     * This is usually the domain username.
     * If not specified, Rampart will fall back to the Rampart configuration's 
     * {@link RampartConfig#getUser() user}.
     * Note that the principal name specified in JAAS configuration takes precedence
     * over any principal name configured here.
     */
    public String getPrincipalName() {
        return principalName;
    }

    /**
     * Sets the principal name to use to obtain a TGT (Ticket granting ticket). 
     * This is usually the domain username. If* not specified, Rampart will fall back 
     * to the Rampart configuration's {@link RampartConfig#getUser() user}. 
     * Note that the principal name specified in JAAS configuration takes precedence 
     * over any principal name configured via this method.
     * @param principalName the principalName to set
     */
    public void setPrincipalName(String principalName) {
        this.principalName = principalName;
    }

    /**
     * @return Returns the principal's clear-text password. If the password is not
     * configured (null), Rampart will try to obtain it from any configured 
     * {@link RampartConfig#getPwCbClass() password callback}. Note that any 
     * principal password configured here will be ignored if the JAAS configuration 
     * configures usage of a keytab file.
     */
    public String getPrincipalPassword() {
        return principalPassword;
    }

    /**
     * Sets the principal's clear-text password. If the password is not configured
     * (null), Rampart will try to obtain it from any configured 
     * {@link RampartConfig#getPwCbClass() password callback}. Note that any 
     * principal password configured here will be ignored if the JAAS configuration 
     * configures usage of a keytab file.
     * @param principalPassword clear-text password
     */
    public void setPrincipalPassword(String principalPassword) {
        this.principalPassword = principalPassword;
    }

    /**
     * @return The service principal name to use to obtain a service ticket on the 
     * client-side. Note that by default, * this name is assumed to be in a {@link #HOST_BASED_NAME_FORM} unless the
     * {@link #setServicePrincipalNameForm(String) service principal name form} is 
     * explicitly configured.
     */
    public String getServicePrincipalName() {
        return servicePrincipalName;
    }

    /**
     * Sets service principal name to use to obtain a service ticket on the 
     * client-side. Note that by default, this name is assumed to be in a 
     * {@link #HOST_BASED_NAME_FORM} unless the 
     * {@link #setServicePrincipalNameForm(String)
     * service principal name form} is explicitly configured.
     * @param servicePrincipalName service principal name
     */
    public void setServicePrincipalName(String servicePrincipalName) {
        this.servicePrincipalName = servicePrincipalName;
    }

    /**
     * Returns the service principal name form.
     * @return Either {@value #HOST_BASED_NAME_FORM} or {@value #USERNAME_NAME_FORM}.   
     * Default is: {@value #HOST_BASED_NAME_FORM}.
     */
    public String getServicePrincipalNameForm() {
        if (servicePrincipalNameForm == null) {
            return HOST_BASED_NAME_FORM;
        }
        return servicePrincipalNameForm;
    }
    
    /**
     * Configures a Kerberos token decoder implementation for decoding Kerberos v5 tokens on server side.
     * The decoder will be used only if the Kerberos client/server session key cannot be obtained using Java's {@link com.sun.security.jgss.ExtendedGSSContext} API,
     * which is normally the case when using Java version older than 1.7.0_b07, 
     * see <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6710360"> JDK-6710360 : export Kerberos session key to applications</a>.
     * <p>
     * The class will be loaded using current service's {@link org.apache.axis2.description.AxisService#getClassLoader() classloader}.
     * </p>
     * 
     * @param kerberosTokenDecoderClass A fully qualifier class name that implements {@link org.apache.wss4j.dom.validate.KerberosTokenValidator}.
     */
    public void setKerberosTokenDecoderClass(String kerberosTokenDecoderClass) {
        this.kerberosTokenDecoderClass = kerberosTokenDecoderClass;
    }
    
    /**
     * Returns the Kerberos token decoder implementation for decoding Kerberos v5 tokens on server side.
     * The decoder will be used only if the Kerberos client/server session key cannot be obtained using Java's {@link com.sun.security.jgss.ExtendedGSSContext} API,
     * which is normally the case when using Java version older than 1.7.0_b07, 
     * see <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6710360"> JDK-6710360 : export Kerberos session key to applications</a>
     * 
     * @return A fully qualifier class name that implements {@link org.apache.wss4j.dom.validate.KerberosTokenValidator} or <code>null</code> if no Kerberos token decoder is configured.
     */
    public String getKerberosTokenDecoderClass() {
        return this.kerberosTokenDecoderClass;
    }

    /**
     * Sets the service principal name form.
     * @param servicePrincipalNameForm The service principal name form to set. 
     * The given literal must be either {@value #HOST_BASED_NAME_FORM} or 
     * {@value #USERNAME_NAME_FORM}.
     * @throws IllegalArgumentException If the given 
     * <code>servicePrincipalNameForm</code> is not one of:
     * {@value #HOST_BASED_NAME_FORM} or {@value #USERNAME_NAME_FORM}.
     */
    public void setServicePrincipalNameForm(String servicePrincipalNameForm) 
        throws IllegalArgumentException {

        if (!HOST_BASED_NAME_FORM.equals(servicePrincipalNameForm) &&
            !USERNAME_NAME_FORM.equals(servicePrincipalNameForm)) {
            throw new IllegalArgumentException(
               new RampartException("invalidServicePrincipalNameForm", 
                                    new String[] {
                                        servicePrincipalNameForm, 
                                        HOST_BASED_NAME_FORM, 
                                        USERNAME_NAME_FORM }));
        }
        this.servicePrincipalNameForm = servicePrincipalNameForm;
    }
    
    /**
     * If Kerberos credential delegation is requested, the initiator's TGT (Ticket granting ticket) is propagated to the receiver
     * along with the TGS(Ticket granting service).
     * 
     * @return true if credential delegation is requested.
     */
    public boolean isRequstCredentialDelegation() {
        return requstCredentialDelegation;
    }

    /**
     * Enables Kerberos credential delegation. If credential delegation is requested, the initiator's TGT (Ticket
     * granting ticket) is propagated to the receiver along with the TGS(Ticket granting service).
     * <p>
     * Enabling delegation requires <b>forwardable=true</b> property to be added to the <b>[libdefaults]</b> section in
     * the Kerberos setup configuration.KDC should also be explicitly configured to allow delegation as it is considered
     * a security issue and is disabled by default.
     * 
     * @param requstCredentialDelegation if true, credential delegation is requested.
     */
    public void setRequstCredentialDelegation(boolean requstCredentialDelegation) {
        this.requstCredentialDelegation = requstCredentialDelegation;
    }

    /**
     * The delegation credential is available when the initiator has explicitly requested delegation through
     * {@link KerberosConfig#setRequstCredentialDelegation(boolean)} and the receiver has retrieved it and set it
     * through {@link KerberosConfig#setDelegationCredential(GSSCredential)}.
     * 
     * If available, the delegation credential is used by the receiver to obtain a service ticket for another
     * Kerberos protected WS on behalf of the initiator. The receiver's principal must have explicit privileges to use
     * the delegated credential(TGT) for retrieval of the service ticket.
     * 
     * @return the client's TGT wrapped in {@link GSSCredential}
     */
    public GSSCredential getDelegationCredential() {
        return delegationCredential;
    }

    /**
     * Sets the delegation credential to be used by the receiver to obtain a service ticket for another
     * Kerberos protected WS on behalf of the initiator. The receiver's principal must have explicit privileges to use
     * the delegated credential(TGT) for retrieval of the service ticket.
     * 
     * @param delegationCredential the {@link GSSCredential} to use for obtaining a TGS
     */
    public void setDelegationCredential(GSSCredential delegationCredential) {
        this.delegationCredential = delegationCredential;
    }

    public PolicyComponent normalize() {
        throw new UnsupportedOperationException();
    }

    public QName getName() {
        return new QName(RampartConfig.NS, KERBEROS_LN);
    }

    public boolean isOptional() {
        return true;
    }

    public boolean isIgnorable() {
        throw new UnsupportedOperationException();
    }

    public short getType() {
        return Constants.TYPE_ASSERTION;
    }

    public boolean equal(PolicyComponent policyComponent) {
        throw new UnsupportedOperationException();
    }

    public void serialize(XMLStreamWriter writer) throws XMLStreamException {
        String prefix = writer.getPrefix(RampartConfig.NS);

        if (prefix == null) {
            prefix = RampartConfig.NS;
            writer.setPrefix(prefix, RampartConfig.NS);
        }

        if (getJaasContext() != null) {
            writer.writeStartElement(RampartConfig.NS, JAAS_CONTEXT_LN);
            writer.writeCharacters(getJaasContext());
            writer.writeEndElement();
        }

        if (getPrincipalName() != null) {
            writer.writeStartElement(RampartConfig.NS, PRINCIPAL_NAME_LN);
            writer.writeCharacters(getPrincipalName());
            writer.writeEndElement();
        }

        if (getPrincipalPassword() != null) {
            writer.writeStartElement(RampartConfig.NS, PRINCIPAL_PASSWORD_LN);
            writer.writeCharacters(getPrincipalPassword());
            writer.writeEndElement();
        }

        if (getServicePrincipalName() != null) {
            writer.writeStartElement(RampartConfig.NS, SERVICE_PRINCIPAL_NAME_LN);
            writer.writeCharacters(getServicePrincipalName());
            writer.writeEndElement();
        }

        if (this.servicePrincipalNameForm != null) {
            writer.writeStartElement(RampartConfig.NS,
                                     SERVICE_PRINCIPAL_NAME_FORM_LN);
            writer.writeCharacters(this.servicePrincipalNameForm);
            writer.writeEndElement();
        }
        
        if (this.kerberosTokenDecoderClass != null) {
            writer.writeStartElement(RampartConfig.NS,
                                     KERBEROS_TOKEN_DECODER_CLASS_LN);
            writer.writeCharacters(this.kerberosTokenDecoderClass);
            writer.writeEndElement();
        }
        
        if (this.requstCredentialDelegation) {
            writer.writeStartElement(RampartConfig.NS, REQUEST_CREDENTIAL_DELEGATION_LN);
            writer.writeCharacters(Boolean.toString(this.requstCredentialDelegation));
            writer.writeEndElement();
        }
    }
}
