aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-application
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2014-04-18 13:27:05 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2014-04-18 13:27:05 +0200
commitc41842fc961af612d03ab6f21d47fd05c2c070d8 (patch)
tree56b50a4f62508f7653ba5180c73a1f8ca79f5434 /sonar-application
parentb6083abec8c95e5bb7c68fb83d09fe40d154e23d (diff)
parenteebc6d31b2750c1aae890e716c2c37854543980f (diff)
downloadsonarqube-c41842fc961af612d03ab6f21d47fd05c2c070d8.tar.gz
sonarqube-c41842fc961af612d03ab6f21d47fd05c2c070d8.zip
Merge branch medium-tests
Diffstat (limited to 'sonar-application')
-rw-r--r--sonar-application/assembly.xml17
-rw-r--r--sonar-application/pom.xml47
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties2
-rw-r--r--sonar-application/src/main/assembly/conf/wrapper.conf9
-rw-r--r--sonar-application/src/main/java/org/sonar/application/AesCipher.java138
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Base64Cipher.java35
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Cipher.java26
-rw-r--r--sonar-application/src/main/java/org/sonar/application/ConfigurationUtils.java49
-rw-r--r--sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java1
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Encryption.java66
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Props.java33
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AesCipherTest.java186
-rw-r--r--sonar-application/src/test/java/org/sonar/application/ConfigurationUtilsTest.java55
-rw-r--r--sonar-application/src/test/java/org/sonar/application/EncryptionTest.java59
-rw-r--r--sonar-application/src/test/java/org/sonar/application/PropsTest.java10
-rw-r--r--sonar-application/src/test/resources/org/sonar/application/AesCipherTest/aes_secret_key.txt1
-rw-r--r--sonar-application/src/test/resources/org/sonar/application/AesCipherTest/bad_secret_key.txt1
-rw-r--r--sonar-application/src/test/resources/org/sonar/application/AesCipherTest/non_trimmed_secret_key.txt3
-rw-r--r--sonar-application/src/test/resources/org/sonar/application/AesCipherTest/other_secret_key.txt1
-rw-r--r--sonar-application/src/test/resources/org/sonar/application/PropsTest/sonar.properties1
20 files changed, 692 insertions, 48 deletions
diff --git a/sonar-application/assembly.xml b/sonar-application/assembly.xml
index d99cf5fedb4..8010c915144 100644
--- a/sonar-application/assembly.xml
+++ b/sonar-application/assembly.xml
@@ -13,13 +13,20 @@
<excludes>
<exclude>org.codehaus.sonar:sonar-server</exclude>
<exclude>mysql:mysql-connector-java</exclude>
- <exclude>com.h2database:h2</exclude>
<exclude>postgresql:postgresql</exclude>
<exclude>net.sourceforge.jtds:jtds</exclude>
<exclude>org.codehaus.sonar.plugins:*</exclude>
<exclude>org.codehaus.sonar-plugins.*:*</exclude>
+ <exclude>org.codehaus.sonar:sonar-batch-maven-compat</exclude>
</excludes>
</dependencySet>
+ <dependencySet>
+ <outputDirectory>lib/batch</outputDirectory>
+ <useTransitiveDependencies>false</useTransitiveDependencies>
+ <includes>
+ <include>org.codehaus.sonar:sonar-batch-maven-compat</include>
+ </includes>
+ </dependencySet>
<!-- Plugins -->
<dependencySet>
<outputDirectory>lib/core-plugins</outputDirectory>
@@ -53,14 +60,6 @@
<scope>runtime</scope>
</dependencySet>
<dependencySet>
- <outputDirectory>extensions/jdbc-driver/h2/</outputDirectory>
- <includes>
- <include>com.h2database:h2</include>
- </includes>
- <unpack>false</unpack>
- <scope>runtime</scope>
- </dependencySet>
- <dependencySet>
<outputDirectory>extensions/jdbc-driver/postgresql/</outputDirectory>
<includes>
<include>postgresql:postgresql</include>
diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml
index 3bceb25828e..83058a4ead5 100644
--- a/sonar-application/pom.xml
+++ b/sonar-application/pom.xml
@@ -17,15 +17,27 @@
<dependencies>
<dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
@@ -46,6 +58,12 @@
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>sonar-batch-maven-compat</artifactId>
+ <version>${pom.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
@@ -75,11 +93,6 @@
<scope>runtime</scope>
</dependency>
<dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<scope>runtime</scope>
@@ -134,13 +147,6 @@
</dependency>
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
- <artifactId>sonar-l10n-en-plugin</artifactId>
- <version>${project.version}</version>
- <type>sonar-plugin</type>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-email-notifications-plugin</artifactId>
<version>${project.version}</version>
<type>sonar-plugin</type>
@@ -159,13 +165,6 @@
<scope>runtime</scope>
</dependency>
<dependency>
- <groupId>org.codehaus.sonar.plugins</groupId>
- <artifactId>sonar-maven-batch-plugin</artifactId>
- <version>${project.version}</version>
- <type>sonar-plugin</type>
- <scope>runtime</scope>
- </dependency>
- <dependency>
<groupId>org.sonatype.jsw-binaries</groupId>
<artifactId>jsw-binaries</artifactId>
<version>3.2.3.6</version>
@@ -201,12 +200,6 @@
<artifactId>http-request</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <scope>test</scope>
- </dependency>
-
</dependencies>
<build>
@@ -259,8 +252,8 @@
<configuration>
<rules>
<requireFilesSize>
- <minsize>55000000</minsize>
- <maxsize>75000000</maxsize>
+ <minsize>80000000</minsize>
+ <maxsize>88000000</maxsize>
<files>
<file>${project.build.directory}/sonarqube-${project.version}.zip</file>
</files>
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index 6c058aa3227..65954622bb9 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -82,7 +82,7 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000
# Web context. When set, it must start with forward slash (for example /sonarqube).
# The default value is root context (empty value).
-#sonar.web.context=
+#sonar.web.context=/
# TCP port for incoming HTTP connections. Disabled when value is -1.
#sonar.web.port=9000
diff --git a/sonar-application/src/main/assembly/conf/wrapper.conf b/sonar-application/src/main/assembly/conf/wrapper.conf
index 9353b41cd83..d53aa251978 100644
--- a/sonar-application/src/main/assembly/conf/wrapper.conf
+++ b/sonar-application/src/main/assembly/conf/wrapper.conf
@@ -33,11 +33,10 @@ wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
# needed starting from 1
wrapper.java.classpath.1=../../lib/*.jar
wrapper.java.classpath.2=../../conf
-wrapper.java.classpath.3=../../extensions/jdbc-driver/h2/*.jar
-wrapper.java.classpath.4=../../extensions/jdbc-driver/mysql/*.jar
-wrapper.java.classpath.5=../../extensions/jdbc-driver/oracle/*.jar
-wrapper.java.classpath.6=../../extensions/jdbc-driver/postgresql/*.jar
-wrapper.java.classpath.7=../../extensions/jdbc-driver/mssql/*.jar
+wrapper.java.classpath.3=../../extensions/jdbc-driver/mysql/*.jar
+wrapper.java.classpath.4=../../extensions/jdbc-driver/oracle/*.jar
+wrapper.java.classpath.5=../../extensions/jdbc-driver/postgresql/*.jar
+wrapper.java.classpath.6=../../extensions/jdbc-driver/mssql/*.jar
# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=./lib
diff --git a/sonar-application/src/main/java/org/sonar/application/AesCipher.java b/sonar-application/src/main/java/org/sonar/application/AesCipher.java
new file mode 100644
index 00000000000..e778b0ebc18
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/AesCipher.java
@@ -0,0 +1,138 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.Nullable;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Key;
+import java.security.SecureRandom;
+
+final class AesCipher extends Cipher {
+
+ // Can't be increased because of Java 6 policy files :
+ // https://confluence.terena.org/display/~visser/No+256+bit+ciphers+for+Java+apps
+ // http://java.sun.com/javase/6/webnotes/install/jre/README
+ public static final int KEY_SIZE_IN_BITS = 128;
+
+ private static final String CRYPTO_KEY = "AES";
+
+ /**
+ * Duplication from CoreProperties.ENCRYPTION_SECRET_KEY_PATH
+ */
+ static final String ENCRYPTION_SECRET_KEY_PATH = "sonar.secretKeyPath";
+
+ private String pathToSecretKey;
+
+ AesCipher(@Nullable String pathToSecretKey) {
+ this.pathToSecretKey = pathToSecretKey;
+ }
+
+ @Override
+ String encrypt(String clearText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile());
+ return new String(Base64.encodeBase64(cipher.doFinal(clearText.getBytes("UTF-8"))));
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ String decrypt(String encryptedText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
+ cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile());
+ byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText)));
+ return new String(cipherData);
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ /**
+ * This method checks the existence of the file, but not the validity of the contained key.
+ */
+ boolean hasSecretKey() {
+ String path = getPathToSecretKey();
+ if (StringUtils.isNotBlank(path)) {
+ File file = new File(path);
+ return file.exists() && file.isFile();
+ }
+ return false;
+ }
+
+ private Key loadSecretFile() throws IOException {
+ String path = getPathToSecretKey();
+ return loadSecretFileFromFile(path);
+ }
+
+ @VisibleForTesting
+ Key loadSecretFileFromFile(@Nullable String path) throws IOException {
+ if (StringUtils.isBlank(path)) {
+ throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH);
+ }
+ File file = new File(path);
+ if (!file.exists() || !file.isFile()) {
+ throw new IllegalStateException("The property " + ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path);
+ }
+ String s = FileUtils.readFileToString(file);
+ if (StringUtils.isBlank(s)) {
+ throw new IllegalStateException("No secret key in the file: " + path);
+ }
+ return new SecretKeySpec(Base64.decodeBase64(StringUtils.trim(s)), CRYPTO_KEY);
+ }
+
+ String generateRandomSecretKey() {
+ try {
+ KeyGenerator keyGen = KeyGenerator.getInstance(CRYPTO_KEY);
+ keyGen.init(KEY_SIZE_IN_BITS, new SecureRandom());
+ SecretKey secretKey = keyGen.generateKey();
+ return new String(Base64.encodeBase64(secretKey.getEncoded()));
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to generate secret key", e);
+ }
+ }
+
+ @VisibleForTesting
+ String getPathToSecretKey() {
+ if (StringUtils.isBlank(pathToSecretKey)) {
+ pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath();
+ }
+ return pathToSecretKey;
+ }
+
+ public void setPathToSecretKey(@Nullable String pathToSecretKey) {
+ this.pathToSecretKey = pathToSecretKey;
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/Base64Cipher.java b/sonar-application/src/main/java/org/sonar/application/Base64Cipher.java
new file mode 100644
index 00000000000..5abbeb85ac2
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/Base64Cipher.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+import org.apache.commons.codec.binary.Base64;
+
+final class Base64Cipher extends Cipher {
+ @Override
+ String encrypt(String clearText) {
+ return new String(Base64.encodeBase64(clearText.getBytes()));
+ }
+
+ @Override
+ String decrypt(String encryptedText) {
+ return new String(Base64.decodeBase64(encryptedText));
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/Cipher.java b/sonar-application/src/main/java/org/sonar/application/Cipher.java
new file mode 100644
index 00000000000..44abfbb3176
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/Cipher.java
@@ -0,0 +1,26 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+abstract class Cipher {
+ abstract String encrypt(String clearText);
+ abstract String decrypt(String encryptedText);
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/ConfigurationUtils.java b/sonar-application/src/main/java/org/sonar/application/ConfigurationUtils.java
new file mode 100644
index 00000000000..a5a563a168c
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/ConfigurationUtils.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.application;
+
+import org.apache.commons.lang.text.StrSubstitutor;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+public final class ConfigurationUtils {
+
+ private ConfigurationUtils() {
+ // Utility class
+ }
+
+ static Properties interpolateEnvVariables(Properties properties) {
+ return interpolateVariables(properties, System.getenv());
+ }
+
+ static Properties interpolateVariables(Properties properties, Map<String, String> variables) {
+ Properties result = new Properties();
+ Enumeration keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ String key = (String) keys.nextElement();
+ String value = (String) properties.get(key);
+ String interpolatedValue = StrSubstitutor.replace(value, variables, "${env:", "}");
+ result.setProperty(key, interpolatedValue);
+ }
+ return result;
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
index ddb1efad898..a677f8bcf11 100644
--- a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
+++ b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
@@ -65,6 +65,7 @@ class EmbeddedTomcat {
tomcat.getHost().setDeployOnStartup(true);
Props props = Props.create(env);
+
Logging.configure(tomcat, env, props);
Connectors.configure(tomcat, props);
Webapp.configure(tomcat, env, props);
diff --git a/sonar-application/src/main/java/org/sonar/application/Encryption.java b/sonar-application/src/main/java/org/sonar/application/Encryption.java
new file mode 100644
index 00000000000..60e732fc716
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/Encryption.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+import com.google.common.collect.ImmutableMap;
+
+import javax.annotation.Nullable;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @since 3.0
+ */
+public final class Encryption {
+
+ private static final String BASE64_ALGORITHM = "b64";
+
+ private static final String AES_ALGORITHM = "aes";
+ private final AesCipher aesCipher;
+
+ private final Map<String, Cipher> ciphers;
+ private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)");
+
+ public Encryption(@Nullable String pathToSecretKey) {
+ aesCipher = new AesCipher(pathToSecretKey);
+ ciphers = ImmutableMap.of(
+ BASE64_ALGORITHM, new Base64Cipher(),
+ AES_ALGORITHM, aesCipher);
+ }
+ public boolean isEncrypted(String value) {
+ return value.indexOf('{') == 0 && value.indexOf('}') > 1;
+ }
+
+ public String decrypt(String encryptedText) {
+ Matcher matcher = ENCRYPTED_PATTERN.matcher(encryptedText);
+ if (matcher.matches()) {
+ Cipher cipher = ciphers.get(matcher.group(1).toLowerCase(Locale.ENGLISH));
+ if (cipher != null) {
+ return cipher.decrypt(matcher.group(2));
+ }
+ }
+ return encryptedText;
+ }
+
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/Props.java b/sonar-application/src/main/java/org/sonar/application/Props.java
index 7b33d918cc6..dbe24636d0e 100644
--- a/sonar-application/src/main/java/org/sonar/application/Props.java
+++ b/sonar-application/src/main/java/org/sonar/application/Props.java
@@ -22,16 +22,14 @@ package org.sonar.application;
import org.apache.commons.io.IOUtils;
import javax.annotation.Nullable;
+
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileReader;
-import java.io.IOException;
+import java.util.Map;
import java.util.Properties;
-/**
- * TODO support env substitution and encryption
- */
class Props {
+
private final Properties props;
Props(Properties props) {
@@ -80,8 +78,18 @@ class Props {
FileReader reader = null;
try {
reader = new FileReader(propsFile);
+
+ // order is important : the last override the first
p.load(reader);
+ p.putAll(System.getenv());
p.putAll(System.getProperties());
+
+ p = ConfigurationUtils.interpolateEnvVariables(p);
+ p = decrypt(p);
+
+ // Set all properties as system properties to pass them to PlatformServletContextListener
+ System.setProperties(p);
+
return new Props(p);
} catch (Exception e) {
@@ -91,4 +99,19 @@ class Props {
IOUtils.closeQuietly(reader);
}
}
+
+ static Properties decrypt(Properties properties) {
+ Encryption encryption = new Encryption(properties.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH));
+ Properties result = new Properties();
+
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ if (encryption.isEncrypted(value)) {
+ value = encryption.decrypt(value);
+ }
+ result.setProperty(key, value);
+ }
+ return result;
+ }
}
diff --git a/sonar-application/src/test/java/org/sonar/application/AesCipherTest.java b/sonar-application/src/test/java/org/sonar/application/AesCipherTest.java
new file mode 100644
index 00000000000..9f097093105
--- /dev/null
+++ b/sonar-application/src/test/java/org/sonar/application/AesCipherTest.java
@@ -0,0 +1,186 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+import com.google.common.io.Resources;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import javax.crypto.BadPaddingException;
+
+import java.io.File;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+
+public class AesCipherTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void generateRandomSecretKey() {
+ AesCipher cipher = new AesCipher(null);
+
+ String key = cipher.generateRandomSecretKey();
+
+ assertThat(StringUtils.isNotBlank(key)).isTrue();
+ assertThat(Base64.isArrayByteBase64(key.getBytes())).isTrue();
+ }
+
+ @Test
+ public void encrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ String encryptedText = cipher.encrypt("this is a secret");
+
+ assertThat(StringUtils.isNotBlank(encryptedText)).isTrue();
+ assertThat(Base64.isArrayByteBase64(encryptedText.getBytes())).isTrue();
+ }
+
+ @Test
+ public void encrypt_bad_key() throws Exception {
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("Invalid AES key");
+
+ AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt"));
+
+ cipher.encrypt("this is a secret");
+ }
+
+ @Test
+ public void decrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ // the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt
+ String clearText = cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+
+ assertThat(clearText).isEqualTo("this is a secret");
+ }
+
+ @Test
+ public void decrypt_bad_key() throws Exception {
+ AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt"));
+
+ try {
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isInstanceOf(InvalidKeyException.class);
+ }
+ }
+
+ @Test
+ public void decrypt_other_key() throws Exception {
+ AesCipher cipher = new AesCipher(getPath("other_secret_key.txt"));
+
+ try {
+ // text encrypted with another key
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isInstanceOf(BadPaddingException.class);
+ }
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ assertThat(cipher.decrypt(cipher.encrypt("foo"))).isEqualTo("foo");
+ }
+
+ @Test
+ public void testDefaultPathToSecretKey() {
+ AesCipher cipher = new AesCipher(null);
+
+ String path = cipher.getPathToSecretKey();
+
+ assertThat(StringUtils.isNotBlank(path)).isTrue();
+ assertThat(new File(path).getName()).isEqualTo("sonar-secret.txt");
+ }
+
+ @Test
+ public void loadSecretKeyFromFile() throws Exception {
+ AesCipher cipher = new AesCipher(null);
+ Key secretKey = cipher.loadSecretFileFromFile(pathToSecretKey());
+ assertThat(secretKey.getAlgorithm()).isEqualTo("AES");
+ assertThat(secretKey.getEncoded().length).isGreaterThan(10);
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_trim_content() throws Exception {
+ String path = getPath("non_trimmed_secret_key.txt");
+ AesCipher cipher = new AesCipher(null);
+
+ Key secretKey = cipher.loadSecretFileFromFile(path);
+
+ assertThat(secretKey.getAlgorithm()).isEqualTo("AES");
+ assertThat(secretKey.getEncoded().length).isGreaterThan(10);
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_file_does_not_exist() throws Exception {
+ thrown.expect(IllegalStateException.class);
+
+ AesCipher cipher = new AesCipher(null);
+ cipher.loadSecretFileFromFile("/file/does/not/exist");
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_no_property() throws Exception {
+ thrown.expect(IllegalStateException.class);
+
+ AesCipher cipher = new AesCipher(null);
+ cipher.loadSecretFileFromFile(null);
+ }
+
+ @Test
+ public void hasSecretKey() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ assertThat(cipher.hasSecretKey()).isTrue();
+ }
+
+ @Test
+ public void doesNotHaveSecretKey() throws Exception {
+ AesCipher cipher = new AesCipher("/my/twitter/id/is/SimonBrandhof");
+
+ assertThat(cipher.hasSecretKey()).isFalse();
+ }
+
+ private static String getPath(String file){
+ return Resources.getResource(AesCipherTest.class, "AesCipherTest/" + file).getPath();
+ }
+
+ private static String pathToSecretKey() throws Exception {
+ return getPath("aes_secret_key.txt");
+ }
+
+}
diff --git a/sonar-application/src/test/java/org/sonar/application/ConfigurationUtilsTest.java b/sonar-application/src/test/java/org/sonar/application/ConfigurationUtilsTest.java
new file mode 100644
index 00000000000..29a722b9fad
--- /dev/null
+++ b/sonar-application/src/test/java/org/sonar/application/ConfigurationUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.application;
+
+import com.google.common.collect.Maps;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.Properties;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ConfigurationUtilsTest {
+ @Test
+ public void shouldInterpolateVariables() {
+ Properties input = new Properties();
+ input.setProperty("hello", "world");
+ input.setProperty("url", "${env:SONAR_JDBC_URL}");
+ input.setProperty("do_not_change", "${SONAR_JDBC_URL}");
+ Map<String, String> variables = Maps.newHashMap();
+ variables.put("SONAR_JDBC_URL", "jdbc:h2:mem");
+
+ Properties output = ConfigurationUtils.interpolateVariables(input, variables);
+
+ assertThat(output.size(), is(3));
+ assertThat(output.getProperty("hello"), is("world"));
+ assertThat(output.getProperty("url"), is("jdbc:h2:mem"));
+ assertThat(output.getProperty("do_not_change"), is("${SONAR_JDBC_URL}"));
+
+ // input is not changed
+ assertThat(input.size(), is(3));
+ assertThat(input.getProperty("hello"), is("world"));
+ assertThat(input.getProperty("url"), is("${env:SONAR_JDBC_URL}"));
+ assertThat(input.getProperty("do_not_change"), is("${SONAR_JDBC_URL}"));
+ }
+
+}
diff --git a/sonar-application/src/test/java/org/sonar/application/EncryptionTest.java b/sonar-application/src/test/java/org/sonar/application/EncryptionTest.java
new file mode 100644
index 00000000000..85b3c568a35
--- /dev/null
+++ b/sonar-application/src/test/java/org/sonar/application/EncryptionTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.application;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class EncryptionTest {
+
+ @Test
+ public void isEncrypted() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.isEncrypted("{aes}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{b64}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{abc}ADASDASAD"), is(true));
+
+ assertThat(encryption.isEncrypted("{}"), is(false));
+ assertThat(encryption.isEncrypted("{foo"), is(false));
+ assertThat(encryption.isEncrypted("foo{aes}"), is(false));
+ }
+
+ @Test
+ public void decrypt() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("{b64}Zm9v"), is("foo"));
+ }
+
+ @Test
+ public void decrypt_unknown_algorithm() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("{xxx}Zm9v"), is("{xxx}Zm9v"));
+ }
+
+ @Test
+ public void decrypt_uncrypted_text() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("foo"), is("foo"));
+ }
+}
diff --git a/sonar-application/src/test/java/org/sonar/application/PropsTest.java b/sonar-application/src/test/java/org/sonar/application/PropsTest.java
index 92091740301..46ca5e8dd26 100644
--- a/sonar-application/src/test/java/org/sonar/application/PropsTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/PropsTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.application;
+import com.google.common.io.Resources;
import org.apache.commons.io.FilenameUtils;
import org.junit.Test;
@@ -31,6 +32,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class PropsTest {
+
@Test
public void of() throws Exception {
Properties p = new Properties();
@@ -99,8 +101,10 @@ public class PropsTest {
@Test
public void load_file_and_system_properties() throws Exception {
+ System.setProperty("hello", "bar");
+
Env env = mock(Env.class);
- File propsFile = new File(getClass().getResource("/org/sonar/application/PropsTest/sonar.properties").toURI());
+ File propsFile = new File(Resources.getResource(getClass(), "PropsTest/sonar.properties").getFile());
when(env.file("conf/sonar.properties")).thenReturn(propsFile);
Props props = Props.create(env);
@@ -109,7 +113,11 @@ public class PropsTest {
assertThat(props.of("java.version")).isNotNull();
// system properties override file properties
+ assertThat(props.of("hello")).isEqualTo("bar");
assertThat(props.of("java.io.tmpdir")).isNotEmpty().isNotEqualTo("/should/be/overridden");
+
+ assertThat(System.getProperty("foo")).isEqualTo("bar");
+ assertThat(System.getProperty("hello")).isEqualTo("bar");
}
@Test
diff --git a/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/aes_secret_key.txt b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/aes_secret_key.txt
new file mode 100644
index 00000000000..65b98c522da
--- /dev/null
+++ b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/aes_secret_key.txt
@@ -0,0 +1 @@
+0PZz+G+f8mjr3sPn4+AhHg== \ No newline at end of file
diff --git a/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/bad_secret_key.txt b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/bad_secret_key.txt
new file mode 100644
index 00000000000..b33e179e5c8
--- /dev/null
+++ b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/bad_secret_key.txt
@@ -0,0 +1 @@
+badbadbad== \ No newline at end of file
diff --git a/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/non_trimmed_secret_key.txt b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/non_trimmed_secret_key.txt
new file mode 100644
index 00000000000..ab83e4adc03
--- /dev/null
+++ b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/non_trimmed_secret_key.txt
@@ -0,0 +1,3 @@
+
+ 0PZz+G+f8mjr3sPn4+AhHg==
+
diff --git a/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/other_secret_key.txt b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/other_secret_key.txt
new file mode 100644
index 00000000000..23f5ecf5104
--- /dev/null
+++ b/sonar-application/src/test/resources/org/sonar/application/AesCipherTest/other_secret_key.txt
@@ -0,0 +1 @@
+IBxEUxZ41c8XTxyaah1Qlg== \ No newline at end of file
diff --git a/sonar-application/src/test/resources/org/sonar/application/PropsTest/sonar.properties b/sonar-application/src/test/resources/org/sonar/application/PropsTest/sonar.properties
index b73be15411b..5c06e58a32e 100644
--- a/sonar-application/src/test/resources/org/sonar/application/PropsTest/sonar.properties
+++ b/sonar-application/src/test/resources/org/sonar/application/PropsTest/sonar.properties
@@ -1,2 +1,3 @@
+hello: world
foo=bar
java.io.tmpdir=/should/be/overridden