/*
* Copyright 2017 ThoughtWorks, Inc.
*
* 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
*
* 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 com.thoughtworks.go.config;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.security.GoCipher;
import com.thoughtworks.go.util.command.EnvironmentVariableContext;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
public class EnvironmentVariableConfigTest {
private GoCipher goCipher;
@Before
public void setUp() {
goCipher = mock(GoCipher.class);
}
@Test
public void shouldEncryptValueWhenConstructedAsSecure() throws InvalidCipherTextException {
GoCipher goCipher = mock(GoCipher.class);
String encryptedText = "encrypted";
when(goCipher.encrypt("password")).thenReturn(encryptedText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher);
HashMap attrs = getAttributeMap("password", "true", "true");
environmentVariableConfig.setConfigAttributes(attrs);
assertThat(environmentVariableConfig.getEncryptedValue(), is(encryptedText));
assertThat(environmentVariableConfig.getName(), is("foo"));
assertThat(environmentVariableConfig.isSecure(), is(true));
}
@Test
public void shouldAssignNameAndValueForAVanillaEnvironmentVariable() {
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig((GoCipher) null);
HashMap attrs = new HashMap();
attrs.put(EnvironmentVariableConfig.NAME, "foo");
attrs.put(EnvironmentVariableConfig.VALUE, "password");
environmentVariableConfig.setConfigAttributes(attrs);
assertThat(environmentVariableConfig.getValue(), is("password"));
assertThat(environmentVariableConfig.getName(), is("foo"));
assertThat(environmentVariableConfig.isSecure(), is(false));
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowUpWhenTheAttributeMapHasBothNameAndValueAreEmpty() throws InvalidCipherTextException {
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig((GoCipher) null);
HashMap attrs = new HashMap();
attrs.put(EnvironmentVariableConfig.VALUE, "");
environmentVariableConfig.setConfigAttributes(attrs);
}
@Test
public void shouldGetPlainTextValueFromAnEncryptedValue() throws InvalidCipherTextException {
GoCipher mockGoCipher = mock(GoCipher.class);
String plainText = "password";
String cipherText = "encrypted";
when(mockGoCipher.encrypt(plainText)).thenReturn(cipherText);
when(mockGoCipher.decrypt(cipherText)).thenReturn(plainText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(mockGoCipher);
HashMap attrs = getAttributeMap(plainText, "true", "true");
environmentVariableConfig.setConfigAttributes(attrs);
assertThat(environmentVariableConfig.getValue(), is(plainText));
verify(mockGoCipher).decrypt(cipherText);
}
@Test
public void shouldGetPlainTextValue() throws InvalidCipherTextException {
GoCipher mockGoCipher = mock(GoCipher.class);
String plainText = "password";
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(mockGoCipher);
HashMap attrs = getAttributeMap(plainText, "false", "1");
environmentVariableConfig.setConfigAttributes(attrs);
assertThat(environmentVariableConfig.getValue(), is(plainText));
verify(mockGoCipher, never()).decrypt(anyString());
verify(mockGoCipher, never()).encrypt(anyString());
}
@Test
public void shouldReturnEncryptedValueForSecureVariables() throws InvalidCipherTextException {
when(goCipher.encrypt("bar")).thenReturn("encrypted");
when(goCipher.decrypt("encrypted")).thenReturn("bar");
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "foo", "bar", true);
assertThat(environmentVariableConfig.getName(), is("foo"));
assertThat(environmentVariableConfig.getValue(), is("bar"));
assertThat(environmentVariableConfig.getValueForDisplay(), is(environmentVariableConfig.getEncryptedValue()));
}
@Test
public void shouldReturnValueForInSecureVariables() {
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "foo", "bar", false);
assertThat(environmentVariableConfig.getName(), is("foo"));
assertThat(environmentVariableConfig.getValue(), is("bar"));
assertThat(environmentVariableConfig.getValueForDisplay(), is("bar"));
}
@Test
public void shouldEncryptValueWhenChanged() throws InvalidCipherTextException {
GoCipher mockGoCipher = mock(GoCipher.class);
String plainText = "password";
String cipherText = "encrypted";
when(mockGoCipher.encrypt(plainText)).thenReturn(cipherText);
when(mockGoCipher.decrypt(cipherText)).thenReturn(plainText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(mockGoCipher);
HashMap firstSubmit = getAttributeMap(plainText, "true", "true");
environmentVariableConfig.setConfigAttributes(firstSubmit);
assertThat(environmentVariableConfig.getEncryptedValue(), is(cipherText));
}
@Test
public void shouldRetainEncryptedVariableWhenNotEdited() throws InvalidCipherTextException {
GoCipher mockGoCipher = mock(GoCipher.class);
String plainText = "password";
String cipherText = "encrypted";
when(mockGoCipher.encrypt(plainText)).thenReturn(cipherText);
when(mockGoCipher.decrypt(cipherText)).thenReturn(plainText);
when(mockGoCipher.encrypt(cipherText)).thenReturn("SHOULD NOT DO THIS");
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(mockGoCipher);
HashMap firstSubmit = getAttributeMap(plainText, "true", "true");
environmentVariableConfig.setConfigAttributes(firstSubmit);
HashMap secondSubmit = getAttributeMap(cipherText, "true", "false");
environmentVariableConfig.setConfigAttributes(secondSubmit);
assertThat(environmentVariableConfig.getEncryptedValue(), is(cipherText));
assertThat(environmentVariableConfig.getName(), is("foo"));
assertThat(environmentVariableConfig.isSecure(), is(true));
verify(mockGoCipher, never()).encrypt(cipherText);
}
@Test
public void shouldGetSqlCriteriaForPlainTextEnvironmentVariable() throws InvalidCipherTextException {
String plainText = "value";
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "key", plainText, false);
Map<String, Object> sqlCriteria = environmentVariableConfig.getSqlCriteria();
assertThat(sqlCriteria.get("variableName"), is("key"));
assertThat(sqlCriteria.get("variableValue"), is(plainText));
assertThat(sqlCriteria.get("isSecure"), is(false));
verify(goCipher, never()).encrypt(plainText);
}
@Test
public void shouldGetSqlCriteriaForSecureEnvironmentVariable() throws InvalidCipherTextException {
String encryptedText = "encrypted";
String plainText = "plainText";
when(goCipher.encrypt(plainText)).thenReturn(encryptedText);
when(goCipher.decrypt(encryptedText)).thenReturn(plainText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "key", plainText, true);
Map<String, Object> sqlCriteria = environmentVariableConfig.getSqlCriteria();
assertThat(sqlCriteria.get("variableName"), is("key"));
assertThat(sqlCriteria.get("variableValue"), is(plainText));
assertThat(sqlCriteria.get("isSecure"), is(true));
verify(goCipher).encrypt(plainText);
verify(goCipher).decrypt(encryptedText);
}
@Test
public void shouldAddPlainTextEnvironmentVariableToContext() {
String key = "key";
String plainText = "plainText";
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, key, plainText, false);
EnvironmentVariableContext context = new EnvironmentVariableContext();
environmentVariableConfig.addTo(context);
assertThat(context.getProperty(key), is(plainText));
assertThat(context.getPropertyForDisplay(key), is(plainText));
}
@Test
public void shouldAddSecureEnvironmentVariableToContext() throws InvalidCipherTextException {
String key = "key";
String plainText = "plainText";
String cipherText = "encrypted";
when(goCipher.encrypt(plainText)).thenReturn(cipherText);
when(goCipher.decrypt(cipherText)).thenReturn(plainText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, key, plainText, true);
EnvironmentVariableContext context = new EnvironmentVariableContext();
environmentVariableConfig.addTo(context);
assertThat(context.getProperty(key), is(plainText));
assertThat(context.getPropertyForDisplay(key), is(EnvironmentVariableContext.EnvironmentVariable.MASK_VALUE));
}
@Test
public void shouldAnnotateEnsureEncryptedMethodWithPostConstruct() throws NoSuchMethodException {
Class<EnvironmentVariableConfig> klass = EnvironmentVariableConfig.class;
Method declaredMethods = klass.getDeclaredMethod("ensureEncrypted");
assertThat(declaredMethods.getAnnotation(PostConstruct.class), is(not(nullValue())));
}
@Test
public void shouldErrorOutOnValidateWhenEncryptedValueIsForceChanged() throws InvalidCipherTextException {
String plainText = "secure_value";
String cipherText = "cipherText";
when(goCipher.encrypt(plainText)).thenReturn(cipherText);
when(goCipher.decrypt(cipherText)).thenThrow(new DataLengthException("last block incomplete in decryption"));
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "secure_key", plainText, true);
environmentVariableConfig.validate(null);
ConfigErrors error = environmentVariableConfig.errors();
assertThat(error.isEmpty(), is(false));
assertThat(error.on(EnvironmentVariableConfig.VALUE),
is("Encrypted value for variable named 'secure_key' is invalid. This usually happens when the cipher text is modified to have an invalid value."));
}
@Test
public void shouldNotErrorOutWhenValidationIsSuccessfulForSecureVariables() throws InvalidCipherTextException {
String plainText = "secure_value";
String cipherText = "cipherText";
when(goCipher.encrypt(plainText)).thenReturn(cipherText);
when(goCipher.decrypt(cipherText)).thenReturn(plainText);
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "secure_key", plainText, true);
environmentVariableConfig.validate(null);
assertThat(environmentVariableConfig.errors().isEmpty(), is(true));
}
@Test
public void shouldNotErrorOutWhenValidationIsSuccessfulForPlainTextVariables() {
EnvironmentVariableConfig environmentVariableConfig = new EnvironmentVariableConfig(goCipher, "plain_key", "plain_value", false);
environmentVariableConfig.validate(null);
assertThat(environmentVariableConfig.errors().isEmpty(), is(true));
}
@Test
public void shouldMaskValueIfSecure() {
EnvironmentVariableConfig secureEnvironmentVariable = new EnvironmentVariableConfig(goCipher, "plain_key", "plain_value", true);
Assert.assertThat(secureEnvironmentVariable.getDisplayValue(), is("****"));
}
@Test
public void shouldNotMaskValueIfNotSecure() {
EnvironmentVariableConfig secureEnvironmentVariable = new EnvironmentVariableConfig(goCipher, "plain_key", "plain_value", false);
Assert.assertThat(secureEnvironmentVariable.getDisplayValue(), is("plain_value"));
}
@Test
public void shouldCopyEnvironmentVariableConfig(){
EnvironmentVariableConfig secureEnvironmentVariable = new EnvironmentVariableConfig(goCipher, "plain_key", "plain_value", true);
EnvironmentVariableConfig copy = new EnvironmentVariableConfig(secureEnvironmentVariable);
assertThat(copy.getName(), is(secureEnvironmentVariable.getName()));
assertThat(copy.getValue(), is(secureEnvironmentVariable.getValue()));
assertThat(copy.getEncryptedValue(), is(secureEnvironmentVariable.getEncryptedValue()));
assertThat(copy.isSecure(), is(secureEnvironmentVariable.isSecure()));
}
@Test
public void shouldNotConsiderErrorsForEqualsCheck(){
EnvironmentVariableConfig config1 = new EnvironmentVariableConfig("name", "value");
EnvironmentVariableConfig config2 = new EnvironmentVariableConfig("name", "value");
config2.addError("name", "errrr");
assertThat(config1.equals(config2), is(true));
}
private HashMap getAttributeMap(String value, final String secure, String isChanged) {
HashMap attrs;
attrs = new HashMap();
attrs.put(EnvironmentVariableConfig.NAME, "foo");
attrs.put(EnvironmentVariableConfig.VALUE, value);
attrs.put(EnvironmentVariableConfig.SECURE, secure);
attrs.put(EnvironmentVariableConfig.ISCHANGED, isChanged);
return attrs;
}
@Test
public void shouldDeserializeWithErrorFlagIfAnEncryptedVarialeHasBothClearTextAndCipherText() throws Exception {
EnvironmentVariableConfig variable = new EnvironmentVariableConfig();
variable.deserialize("PASSWORD", "clearText", true, "c!ph3rt3xt");
assertThat(variable.errors().getAllOn("value"), is(Arrays.asList("You may only specify `value` or `encrypted_value`, not both!")));
assertThat(variable.errors().getAllOn("encryptedValue"), is(Arrays.asList("You may only specify `value` or `encrypted_value`, not both!")));
}
@Test
public void shouldDeserializeWithNoErrorFlagIfAnEncryptedVarialeHasClearTextWithSecureTrue() throws Exception {
EnvironmentVariableConfig variable = new EnvironmentVariableConfig();
variable.deserialize("PASSWORD", "clearText", true, null);
assertTrue(variable.errors().isEmpty());
}
@Test
public void shouldDeserializeWithNoErrorFlagIfAnEncryptedVarialeHasEitherClearTextWithSecureFalse() throws Exception {
EnvironmentVariableConfig variable = new EnvironmentVariableConfig();
variable.deserialize("PASSWORD", "clearText", false, null);
assertTrue(variable.errors().isEmpty());
}
@Test
public void shouldDeserializeWithNoErrorFlagIfAnEncryptedVariableHasCipherTextSetWithSecureTrue() throws Exception {
EnvironmentVariableConfig variable = new EnvironmentVariableConfig();
variable.deserialize("PASSWORD", null, true, "cipherText");
assertTrue(variable.errors().isEmpty());
}
@Test
public void shouldErrorOutForEncryptedValueBeingSetWhenSecureIsFalse() throws Exception {
EnvironmentVariableConfig variable = new EnvironmentVariableConfig();
variable.deserialize("PASSWORD", null, false, "cipherText");
variable.validateTree(null);
assertThat(variable.errors().getAllOn("encryptedValue"), is(Arrays.asList("You may specify encrypted value only when option 'secure' is true.")));
}
}