/*
 *
 * 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.qpid.server.security.auth.sasl;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;

import junit.framework.TestCase;

import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase;
import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser;
import org.apache.qpid.test.utils.TestFileUtils;
import org.apache.qpid.tools.security.Passwd;

/**
 * These tests ensure that the Hex wrapping that the initialiser performs does actually operate when the handle method is called.
 */
public class CRAMMD5HexInitialiserTest extends TestCase
{
    private static final String TEST_PASSWORD = "testPassword";
    private static final String TEST_USER = "testUser";
    private File _file;

    public void testHashedHex() throws Exception
    {
        perform(TEST_USER, getHash(TEST_PASSWORD));
    }

    public void perform(String user, char[] password) throws Exception
    {
        CRAMMD5HexInitialiser initialiser = new CRAMMD5HexInitialiser();

        PrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase();
        db.open(_file);
        initialiser.initialise(db);

        PasswordCallback passwordCallback = new PasswordCallback("password:", false);
        NameCallback usernameCallback = new NameCallback("user:", user);

        Callback[] callbacks = new Callback[]{usernameCallback, passwordCallback};

        assertNull("The password was not null before the handle call.", passwordCallback.getPassword());
        initialiser.getCallbackHandler().handle(callbacks);

        assertArrayEquals(toHex(password), passwordCallback.getPassword());
    }

    public void setUp() throws Exception
    {
        super.setUp();
        _file = TestFileUtils.createTempFile(this, "password-file", new Passwd().getOutput(TEST_USER , TEST_PASSWORD));
    }

    public void tearDown() throws Exception
    {
        if (_file != null)
        {
            _file.delete();
        }
        super.tearDown();
    }

    private char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException
    {

        byte[] data = text.getBytes("utf-8");

        MessageDigest md = MessageDigest.getInstance("MD5");

        for (byte b : data)
        {
            md.update(b);
        }

        byte[] digest = md.digest();

        char[] hash = new char[digest.length];

        int index = 0;
        for (byte b : digest)
        {
            hash[index++] = (char) b;
        }

        return hash;
    }

    private void assertArrayEquals(char[] expected, char[] actual)
    {        
        assertEquals("Arrays are not the same length", expected.length, actual.length);

        for (int index = 0; index < expected.length; index++)
        {
            assertEquals("Characters are not equal", expected[index], actual[index]);
        }
    }

    private char[] toHex(char[] password)
    {
        StringBuilder sb = new StringBuilder();
        for (char c : password)
        {
            //toHexString does not prepend 0 so we have to
            if (((byte) c > -1) && (byte) c < 10)
            {
                sb.append(0);
            }

            sb.append(Integer.toHexString(c & 0xFF));
        }

        //Extract the hex string as char[]
        char[] hex = new char[sb.length()];

        sb.getChars(0, sb.length(), hex, 0);

        return hex;
    }

}
