/*
 * 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.ambari.server.serveraction.kerberos;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;

import java.io.File;
import java.lang.reflect.Method;
import java.nio.charset.Charset;

import jakarta.persistence.EntityManager;

import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.controller.RootComponent;
import org.apache.ambari.server.controller.RootService;
import org.apache.ambari.server.orm.DBAccessor;
import org.apache.ambari.server.orm.dao.HostDAO;
import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO;
import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO.KeytabPrincipalFindOrCreateResult;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity;
import org.apache.ambari.server.serveraction.ActionLog;
import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosPrincipal;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.utils.StageUtils;
import org.apache.commons.io.FileUtils;
import org.easymock.EasyMockSupport;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

import junit.framework.Assert;

public class ConfigureAmbariIdentitiesServerActionTest extends EasyMockSupport {
  @Rule
  public TemporaryFolder testFolder = new TemporaryFolder();

  @Test
  public void installAmbariServerIdentity() throws Exception {
    installAmbariServerIdentity(createNiceMock(ActionLog.class), true);
  }

  @Test
  public void installAmbariServerIdentityWithNoAgentOnAmbariServer() throws Exception {
    installAmbariServerIdentity(createNiceMock(ActionLog.class), false);
  }

  @Test
  public void installAmbariServerIdentityWithNullActionLog() throws Exception {
    installAmbariServerIdentity(null, true);
  }

  private void installAmbariServerIdentity(ActionLog actionLog, boolean ambariServerHasAgent) throws Exception {

    String principal = "ambari-server@EXAMPLE.COM";
    File srcKeytabFile = testFolder.newFile();
    File destKeytabFile = new File(testFolder.getRoot().getAbsolutePath(), "ambari-server.keytab");

    Injector injector = createInjector();

    HostDAO hostDAO = injector.getInstance(HostDAO.class);

    HostEntity hostEntity;
    if (ambariServerHasAgent) {
      hostEntity = createMock(HostEntity.class);
      expect(hostEntity.getHostId()).andReturn(1L).once();
      expect(hostDAO.findById(1L)).andReturn(hostEntity).once();
    } else {
      hostEntity = null;
    }

    expect(hostDAO.findByName(StageUtils.getHostName())).andReturn(hostEntity).once();
    KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO = injector.getInstance(KerberosKeytabPrincipalDAO.class);
    KerberosKeytabPrincipalEntity kkp = createNiceMock(KerberosKeytabPrincipalEntity.class);
    KeytabPrincipalFindOrCreateResult result = new KeytabPrincipalFindOrCreateResult();
    result.created = true;
    result.kkp = kkp;

    expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), eq(hostEntity), anyObject(), anyObject())).andReturn(result).once();
    expect(kerberosKeytabPrincipalDAO.merge(kkp)).andReturn(createNiceMock(KerberosKeytabPrincipalEntity.class)).once();

    // Mock the methods that do the actual file manipulation to avoid having to deal with ambari-sudo.sh used in
    // ShellCommandUtil#mkdir, ShellCommandUtil#copyFile, etc..
    Method methodCopyFile = ConfigureAmbariIdentitiesServerAction.class.getDeclaredMethod("copyFile",
        String.class, String.class);
    Method methodSetFileACL = ConfigureAmbariIdentitiesServerAction.class.getDeclaredMethod("setFileACL",
        String.class, String.class, boolean.class, boolean.class, String.class, boolean.class, boolean.class);

    ConfigureAmbariIdentitiesServerAction action = createMockBuilder(ConfigureAmbariIdentitiesServerAction.class)
        .addMockedMethod(methodCopyFile)
        .addMockedMethod(methodSetFileACL)
        .createMock();

    action.copyFile(srcKeytabFile.getAbsolutePath(), destKeytabFile.getAbsolutePath());
    expectLastCall().once();

    action.setFileACL(destKeytabFile.getAbsolutePath(), "user1", true, true, "groupA", true, false);
    expectLastCall().once();

    replayAll();

    injector.injectMembers(action);
    action.installAmbariServerIdentity(
      new ResolvedKerberosPrincipal(
        null,
        null,
        principal,
        false,
        null,
        RootService.AMBARI.name(),
        RootComponent.AMBARI_SERVER.name(),
        destKeytabFile.getPath()
      ), srcKeytabFile.getAbsolutePath(), destKeytabFile.getAbsolutePath(),
        "user1", "rw", "groupA", "r", actionLog);

    verifyAll();

    // There is no need to verify that the file was copied. We are not testing the ability to copy
    // and we have mocked the method that does the actual copying to avoid having to deal with
    // ambari-sudo.sh via the ShellCommandUtil class.
  }

  @Test
  public void configureJAAS() throws Exception {
    configureJAAS(createNiceMock(ActionLog.class));
  }

  @Test
  public void configureJAASWithNullActionLog() throws Exception {
    configureJAAS(null);
  }

  private void configureJAAS(ActionLog actionLog) throws Exception {
    String principal = "ambari-server@EXAMPLE.COM";
    String keytabFilePath = "/etc/security/keytabs/ambari.server.keytab";

    File jaasConfFile = testFolder.newFile();
    File jaasConfFileBak = new File(jaasConfFile.getAbsolutePath() + ".bak");
    String originalJAASFileContent =
        "com.sun.security.jgss.krb5.initiate {\n" +
            "    com.sun.security.auth.module.Krb5LoginModule required\n" +
            "    renewTGT=false\n" +
            "    doNotPrompt=true\n" +
            "    useKeyTab=true\n" +
            "    keyTab=\"/etc/security/keytabs/ambari.keytab\"\n" +
            "    principal=\"ambari@EXAMPLE.COM\"\n" +
            "    storeKey=true\n" +
            "    useTicketCache=false;\n" +
            "};\n";

    FileUtils.writeStringToFile(jaasConfFile, originalJAASFileContent, Charset.defaultCharset());

    Injector injector = createInjector();

    Method methodGetJAASConfFilePath = ConfigureAmbariIdentitiesServerAction.class.getDeclaredMethod("getJAASConfFilePath");

    ConfigureAmbariIdentitiesServerAction action = createMockBuilder(ConfigureAmbariIdentitiesServerAction.class)
        .addMockedMethod(methodGetJAASConfFilePath)
        .createMock();

    expect(action.getJAASConfFilePath()).andReturn(jaasConfFile.getAbsolutePath());

    replayAll();

    injector.injectMembers(action);
    action.configureJAAS(principal, keytabFilePath, actionLog);

    verifyAll();

    Assert.assertEquals(
        "com.sun.security.jgss.krb5.initiate {\n" +
            "    com.sun.security.auth.module.Krb5LoginModule required\n" +
            "    renewTGT=false\n" +
            "    doNotPrompt=true\n" +
            "    useKeyTab=true\n" +
            "    keyTab=\"/etc/security/keytabs/ambari.server.keytab\"\n" +
            "    principal=\"ambari-server@EXAMPLE.COM\"\n" +
            "    storeKey=true\n" +
            "    useTicketCache=false;\n" +
            "};\n",
            FileUtils.readFileToString(jaasConfFile, Charset.defaultCharset())
    );

    // Ensure the backup file matches the original content
    Assert.assertEquals(originalJAASFileContent,
            FileUtils.readFileToString(jaasConfFileBak, Charset.defaultCharset()));
  }


  private Injector createInjector() {
    return Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
        bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
        bind(AuditLogger.class).toInstance(createNiceMock(AuditLogger.class));
        bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
        bind(KerberosHelper.class).toInstance(createNiceMock(KerberosHelper.class));
        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));

        bind(HostDAO.class).toInstance(createMock(HostDAO.class));
        bind(KerberosKeytabPrincipalDAO.class).toInstance(createMock(KerberosKeytabPrincipalDAO.class));
//        bind(KerberosPrincipalHostDAO.class).toInstance(createMock(KerberosPrincipalHostDAO.class));
      }
    });
  }

}