/*
 *  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.directory.server.kerberos.kdc;


import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.IOException;
import java.util.Collections;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;

import org.apache.directory.api.util.FileUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.DefaultModification;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Modification;
import org.apache.directory.api.ldap.model.entry.ModificationOperation;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.core.api.LdapCoreSessionConnection;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.server.protocol.shared.transport.Transport;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;


/**
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class AbstractKerberosITest extends AbstractLdapTestUnit
{
    public static final String USERS_DN = "ou=users,dc=example,dc=com";
    public static final String REALM = "EXAMPLE.COM";
    public static final String USER_UID = "hnelson";
    public static final String USER_PASSWORD = "secret";
    public static final String LDAP_SERVICE_NAME = "ldap";
    public static final String HOSTNAME = KerberosTestUtils.getHostName();

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    protected LdapCoreSessionConnection conn;


    @Before
    public void setUp() throws Exception
    {
        conn = new LdapCoreSessionConnection( service );

        enableKerberosSchema();
    }


    @After
    public void tearDown() throws Exception
    {
        conn.close();
    }

    class ObtainTicketParameters
    {
        Class<? extends Transport> transport;
        EncryptionType encryptionType;
        ChecksumType checksumType;
        Integer oldUdpPrefLimit;
        Integer oldCksumtypeDefault;


        public ObtainTicketParameters( Class<? extends Transport> transport, EncryptionType encryptionType,
            ChecksumType checksumType )
        {
            this.transport = transport;
            this.encryptionType = encryptionType;
            this.checksumType = checksumType;
        }
    }


    /**
     * Obtains a TGT and service tickets for the user.
     * Also makes some assertions on the received tickets.
     *
     * @param encryptionType the encryption type to use
     * @throws Exception
     */
    protected void testObtainTickets( ObtainTicketParameters parameters ) throws Exception
    {
        setupEnv( parameters );
        Subject subject = new Subject();

        KerberosTestUtils.obtainTGT( subject, USER_UID, USER_PASSWORD );

        assertEquals( 1, subject.getPrivateCredentials().size() );
        assertEquals( 0, subject.getPublicCredentials().size() );

        KerberosTestUtils.obtainServiceTickets( subject, USER_UID, LDAP_SERVICE_NAME, HOSTNAME );

        assertEquals( 2, subject.getPrivateCredentials().size() );
        assertEquals( 0, subject.getPublicCredentials().size() );
        
        for ( KerberosTicket kt : subject.getPrivateCredentials( KerberosTicket.class ) )
        {
            // System.out.println( kt.getClient() );
            // System.out.println( kt.getServer() );
            // System.out.println( kt.getSessionKeyType() );
            assertEquals( parameters.encryptionType.getValue(), kt.getSessionKeyType() );
        }
    }


    private void enableKerberosSchema() throws LdapException
    {
        Modification mod = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE,
            "m-disabled", "FALSE" );
        conn.modify( "cn=Krb5kdc,ou=schema", mod );
    }


    protected void setupEnv( ObtainTicketParameters parameters )
        throws Exception
    {
        // create krb5.conf with proper encryption type
        String krb5confPath = createKrb5Conf( parameters.checksumType, parameters.encryptionType, parameters.transport == TcpTransport.class );
        System.setProperty( "java.security.krb5.conf", krb5confPath );
        
        // change encryption type in KDC
        kdcServer.getConfig().setEncryptionTypes( Collections.singleton( parameters.encryptionType ) );

        // create principals
        createPrincipal( "uid=" + USER_UID, "Last", "First Last",
            USER_UID, USER_PASSWORD, USER_UID + "@" + REALM );

        createPrincipal( "uid=krbtgt", "KDC Service", "KDC Service",
            "krbtgt", "secret", "krbtgt/" + REALM + "@" + REALM );

        String servicePrincipal = LDAP_SERVICE_NAME + "/" + HOSTNAME + "@" + REALM;
        createPrincipal( "uid=ldap", "Service", "LDAP Service",
            "ldap", "randall", servicePrincipal );
    }


    /**
     * Creates the krb5.conf file for the test.
     * 
     * It looks similar to this:
     * 
     * <pre>
     * [libdefaults]
     *     default_realm = EXAMPLE.COM
     *     default_tkt_enctypes = aes256-cts-hmac-sha1-96
     *     default_tgs_enctypes = aes256-cts-hmac-sha1-96
     *     permitted_enctypes = aes256-cts-hmac-sha1-96
     * 
     * [realms]
     *     EXAMPLE.COM = {
     *         kdc = localhost:6088
     *     }
     * 
     * [domain_realm]
     *     .example.com = EXAMPLE.COM
     *     example.com = EXAMPLE.COM
     * </pre>
     *
     * @param encryptionType
     * @param checksumType
     * @return the path to the krb5.conf file
     * @throws IOException
     */
    private String createKrb5Conf( ChecksumType checksumType, EncryptionType encryptionType, boolean isTcp ) throws IOException
    {
        File file = folder.newFile( "krb5.conf" );

        String data = "";

        data += "[libdefaults]" + SystemUtils.LINE_SEPARATOR;
        data += "default_realm = " + REALM + SystemUtils.LINE_SEPARATOR;
        data += "default_tkt_enctypes = " + encryptionType.getName() + SystemUtils.LINE_SEPARATOR;
        data += "default_tgs_enctypes = " + encryptionType.getName() + SystemUtils.LINE_SEPARATOR;
        data += "permitted_enctypes = " + encryptionType.getName() + SystemUtils.LINE_SEPARATOR;
        //        data += "default_checksum = " + checksumType.getName() + SystemUtils.LINE_SEPARATOR;
        //        data += "ap_req_checksum_type = " + checksumType.getName() + SystemUtils.LINE_SEPARATOR;
        data += "default-checksum_type = " + checksumType.getName() + SystemUtils.LINE_SEPARATOR;
        
        if ( isTcp )
        {
            data += "udp_preference_limit = 1" + SystemUtils.LINE_SEPARATOR;
        }
            

        data += "[realms]" + SystemUtils.LINE_SEPARATOR;
        data += REALM + " = {" + SystemUtils.LINE_SEPARATOR;
        data += "kdc = " + HOSTNAME + ":" + kdcServer.getTransports()[0].getPort() + SystemUtils.LINE_SEPARATOR;
        data += "}" + SystemUtils.LINE_SEPARATOR;

        data += "[domain_realm]" + SystemUtils.LINE_SEPARATOR;
        data += "." + Strings.toLowerCaseAscii( REALM ) + " = " + REALM + SystemUtils.LINE_SEPARATOR;
        data += Strings.toLowerCaseAscii( REALM ) + " = " + REALM + SystemUtils.LINE_SEPARATOR;

        FileUtils.writeStringToFile( file, data );

        return file.getAbsolutePath();
    }


    private void createPrincipal( String rdn, String sn, String cn,
        String uid, String userPassword, String principalName ) throws LdapException
    {
        Entry entry = new DefaultEntry();
        entry.setDn( rdn + "," + USERS_DN );
        entry.add( "objectClass", "top", "person", "inetOrgPerson", "krb5principal", "krb5kdcentry" );
        entry.add( "cn", cn );
        entry.add( "sn", sn );
        entry.add( "uid", uid );
        entry.add( "userPassword", userPassword );
        entry.add( "krb5PrincipalName", principalName );
        entry.add( "krb5KeyVersionNumber", "0" );
        conn.add( entry );
    }

}
