/*
 * Copyright 2015-2019 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.cloud.config.server.ssh;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import com.jcraft.jsch.HostKey;
import com.jcraft.jsch.HostKeyRepository;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.ProxyHTTP;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import org.springframework.cloud.config.server.environment.JGitEnvironmentProperties;
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

/**
 * Unit tests for property based SSH config processor.
 *
 * @author William Tran
 * @author Ollie Hughes
 */
@RunWith(MockitoJUnitRunner.class)
public class PropertyBasedSshSessionFactoryTest {

	private static final String HOST_KEY = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAAB"
			+ "BBMzCa0AcNbahUFjFYJHIilhJOhKFHuDOOuY+/HqV9kALftitwNYo6dQ+tC9IK5JVZCZfqKfDWVMxspcPDf9eMoE=";

	private static final String HOST_KEY_ALGORITHM = "ecdsa-sha2-nistp256";

	private static final String PRIVATE_KEY = getResourceAsString("/ssh/key");

	private PropertyBasedSshSessionFactory factory;

	@Mock
	private Host hc;

	@Mock
	private Session session;

	@Mock
	private JSch jSch;

	@Mock
	private HostKeyRepository hostKeyRepository;

	@Mock
	private ProxyHTTP proxyMock;

	public static String getResourceAsString(String path) {
		try {
			Resource resource = new ClassPathResource(path);
			try (BufferedReader br = new BufferedReader(
					new InputStreamReader(resource.getInputStream()))) {
				StringBuilder builder = new StringBuilder();
				String line;
				while ((line = br.readLine()) != null) {
					builder.append(line).append('\n');
				}
				return builder.toString();
			}
		}
		catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}

	@Test
	public void strictHostKeyCheckingIsOptional() {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
		sshKey.setPrivateKey(PRIVATE_KEY);
		setupSessionFactory(sshKey);

		this.factory.configure(this.hc, this.session);

		verify(this.session).setConfig("StrictHostKeyChecking", "no");
		verifyNoMoreInteractions(this.session);
	}

	@Test
	public void strictHostKeyCheckingIsUsed() {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
		sshKey.setHostKey(HOST_KEY);
		sshKey.setPrivateKey(PRIVATE_KEY);
		setupSessionFactory(sshKey);

		this.factory.configure(this.hc, this.session);

		verify(this.session).setConfig("StrictHostKeyChecking", "yes");
		verifyNoMoreInteractions(this.session);
	}

	@Test
	public void hostKeyAlgorithmIsSpecified() {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
		sshKey.setHostKeyAlgorithm(HOST_KEY_ALGORITHM);
		sshKey.setHostKey(HOST_KEY);
		sshKey.setPrivateKey(PRIVATE_KEY);
		setupSessionFactory(sshKey);

		this.factory.configure(this.hc, this.session);
		verify(this.session).setConfig("server_host_key", HOST_KEY_ALGORITHM);
		verify(this.session).setConfig("StrictHostKeyChecking", "yes");
		verifyNoMoreInteractions(this.session);
	}

	@Test
	public void privateKeyIsUsed() throws Exception {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("[email protected]:someorg/somerepo.git");
		sshKey.setPrivateKey(PRIVATE_KEY);
		setupSessionFactory(sshKey);

		this.factory.createSession(this.hc, null,
				SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
		verify(this.jSch).addIdentity("gitlab.example.local", PRIVATE_KEY.getBytes(),
				null, null);
	}

	@Test
	public void hostKeyIsUsed() throws Exception {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("[email protected]:someorg/somerepo.git");
		sshKey.setHostKey(HOST_KEY);
		sshKey.setPrivateKey(PRIVATE_KEY);
		setupSessionFactory(sshKey);

		this.factory.createSession(this.hc, null,
				SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
		ArgumentCaptor<HostKey> captor = ArgumentCaptor.forClass(HostKey.class);
		verify(this.hostKeyRepository).add(captor.capture(), isNull());
		HostKey hostKey = captor.getValue();
		assertThat(hostKey.getHost()).isEqualTo("gitlab.example.local");
		assertThat(hostKey.getKey()).isEqualTo(HOST_KEY);
	}

	@Test
	public void preferredAuthenticationsIsSpecified() {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
		sshKey.setPrivateKey(PRIVATE_KEY);
		sshKey.setPreferredAuthentications("password,keyboard-interactive");
		setupSessionFactory(sshKey);

		this.factory.configure(this.hc, this.session);
		verify(this.session).setConfig("PreferredAuthentications",
				"password,keyboard-interactive");
		verify(this.session).setConfig("StrictHostKeyChecking", "no");
		verifyNoMoreInteractions(this.session);
	}

	@Test
	public void customKnownHostsFileIsUsed() throws Exception {
		JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
		sshKey.setUri("[email protected]:someorg/somerepo.git");
		sshKey.setPrivateKey(PRIVATE_KEY);
		sshKey.setKnownHostsFile("/ssh/known_hosts");
		setupSessionFactory(sshKey);

		this.factory.createSession(this.hc, null,
				SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
		ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);

		verify(this.jSch).setKnownHosts(captor.capture());
		assertThat(captor.getValue()).isEqualTo("/ssh/known_hosts");
	}

	@Test
	public void proxySettingsIsUsed() {
		JGitEnvironmentProperties sshProperties = new JGitEnvironmentProperties();
		sshProperties.setPrivateKey(PRIVATE_KEY);
		Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> map = new HashMap<>();
		ProxyHostProperties proxyHostProperties = new ProxyHostProperties();
		proxyHostProperties.setUsername("user");
		proxyHostProperties.setPassword("password");
		map.put(ProxyHostProperties.ProxyForScheme.HTTP, proxyHostProperties);

		sshProperties.setProxy(map);
		setupSessionFactory(sshProperties);

		this.factory.configure(this.hc, this.session);
		ArgumentCaptor<ProxyHTTP> captor = ArgumentCaptor.forClass(ProxyHTTP.class);

		verify(this.session).setProxy(captor.capture());
		assertThat(captor.getValue()).isNotNull();
		verify(this.proxyMock).setUserPasswd("user", "password");
	}

	private void setupSessionFactory(JGitEnvironmentProperties sshKey) {
		Map<String, JGitEnvironmentProperties> sshKeysByHostname = new HashMap<>();
		sshKeysByHostname.put(SshUriPropertyProcessor.getHostname(sshKey.getUri()),
				sshKey);
		this.factory = new PropertyBasedSshSessionFactory(sshKeysByHostname, this.jSch) {

			@Override
			protected ProxyHTTP createProxy(ProxyHostProperties proxyHostProperties) {
				return proxyMock;
			}
		};
		when(this.hc.getHostName())
				.thenReturn(SshUriPropertyProcessor.getHostname(sshKey.getUri()));
		when(this.jSch.getHostKeyRepository()).thenReturn(this.hostKeyRepository);
	}

}