*/
package org.sonar.server.platform;
-import com.google.common.collect.Lists;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.sonar.api.utils.Logs;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
+import java.io.UnsupportedEncodingException;
+import java.net.*;
import java.util.Enumeration;
-import java.util.List;
+/**
+ * @since 2.11
+ */
public class ServerKeyGenerator {
/**
*/
static final String VERSION = "1";
- private static final int CHECKSUM_SIZE = 9;
+ static final int CHECKSUM_SIZE = 9;
+
+ private final boolean acceptPrivateAddress;
- public String generate(String organization) {
- return generate(organization, null);
+ public ServerKeyGenerator() {
+ this(false);
}
- public String generate(String organization, String previousKey) {
- List<ServerKey> serverKeys = generateForOrganization(organization);
- String external = null;
- String best = null;
- for (ServerKey serverKey : serverKeys) {
- if (StringUtils.equals(previousKey, serverKey.getKey())) {
- best = serverKey.getKey();
- }
- if (serverKey.isExternal()) {
- // External addresses are prefered to internal addresses.
- external = serverKey.getKey();
+ ServerKeyGenerator(boolean acceptPrivateAddress) {
+ this.acceptPrivateAddress = acceptPrivateAddress;
+ }
+
+ public String generate(String organization, String baseUrl) {
+ return generate(organization, baseUrl, null);
+ }
+
+ public String generate(String organization, String baseUrl, String previousKey) {
+ String key = null;
+ if (StringUtils.isNotBlank(organization) && StringUtils.isNotBlank(baseUrl)) {
+ InetAddress address = extractAddressFromUrl(baseUrl);
+ if (address != null && isFixed(address) && isOwner(address)) {
+ key = toKey(organization, address);
}
}
- if (best == null) {
- if (external!=null) {
- best = external;
- } else if (!serverKeys.isEmpty()) {
- best = serverKeys.get(0).getKey();
+ log(previousKey, key);
+ return key;
+ }
+
+ boolean isOwner(InetAddress address) {
+ try {
+ Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (networkInterfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = networkInterfaces.nextElement();
+ Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress ownedAddress = addresses.nextElement();
+ if (ownedAddress.equals(address)) {
+ return true;
+ }
+ }
}
+ } catch (SocketException e) {
+ LoggerFactory.getLogger(ServerKeyGenerator.class).error("Fail to verify server key. Network interfaces can't be browsed.", e);
}
- log(previousKey, best);
- return best;
+ return false;
+ }
+
+ boolean isFixed(InetAddress address) {
+ // Loopback addresses are in the range 127/8.
+ // Link local addresses are in the range 169.254/16 (IPv4) or fe80::/10 (IPv6). They are "autoconfiguration" addresses.
+ // They can assigned pseudorandomly, so they don't guarantee to be the same between two server startups.
+ return acceptPrivateAddress || (!address.isLoopbackAddress() && !address.isLinkLocalAddress());
}
- private void log(String previousKey, String newKey) {
+ void log(String previousKey, String newKey) {
if (StringUtils.isNotBlank(newKey) && StringUtils.isNotBlank(previousKey) && !previousKey.equals(newKey)) {
LoggerFactory.getLogger(getClass()).warn("Server key has changed. Licensed plugins may be disabled. "
+ "Please check the organization name (Settings page) and the server IP addresses.");
}
}
- List<ServerKey> generateForOrganization(String organization) {
- List<ServerKey> keys = Lists.newArrayList();
- if (StringUtils.isNotBlank(organization)) {
- try {
- Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
- while (networkInterfaces.hasMoreElements()) {
- NetworkInterface networkInterface = networkInterfaces.nextElement();
- Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
- while (addresses.hasMoreElements()) {
- ServerKey key = new ServerKey(organization, addresses.nextElement());
- if (key.isValid()) {
- keys.add(key);
- }
- }
- }
- } catch (SocketException e) {
- LoggerFactory.getLogger(getClass()).error("Fail to generate server key. Network interfaces can't be browsed.", e);
- }
- }
- return keys;
- }
-
- static class ServerKey {
- private String organization;
- private InetAddress address;
-
- ServerKey(String organization, InetAddress address) {
- this.organization = organization;
- this.address = address;
+ InetAddress extractAddressFromUrl(String baseUrl) {
+ if (StringUtils.isBlank(baseUrl)) {
+ return null;
}
+ try {
+ URL url = new URL(baseUrl);
+ return InetAddress.getByName(url.getHost());
- boolean isExternal() {
- return !address.isLoopbackAddress() && !address.isSiteLocalAddress() && !address.isLinkLocalAddress();
- }
-
- boolean isValid() {
- // Loopback addresses are in the range 127/8.
- // Link local addresses are in the range 169.254/16 (IPv4) or fe80::/10 (IPv6). They are "autoconfiguration" addresses.
- // They can assigned pseudorandomly, so they don't guarantee to be the same between two server startups.
- return !address.isLoopbackAddress() && !address.isLinkLocalAddress();
- }
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Server base URL is malformed: " + baseUrl, e);
- String getKey() {
- String key = new StringBuilder().append(organization).append("-").append(address.getHostAddress()).toString();
- return VERSION + DigestUtils.shaHex(key.getBytes()).substring(0, CHECKSUM_SIZE);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- ServerKey serverKey = (ServerKey) o;
- if (!address.equals(serverKey.address)) {
- return false;
- }
- if (!organization.equals(serverKey.organization)) {
- return false;
- }
- return true;
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Server base URL is unknown: " + baseUrl, e);
}
+ }
- @Override
- public int hashCode() {
- int result = organization.hashCode();
- result = 31 * result + address.hashCode();
- return result;
- }
+ String toKey(String organization, InetAddress address) {
+ String key = new StringBuilder().append(organization).append("-").append(address.getHostAddress()).toString();
+ try {
+ return VERSION + DigestUtils.shaHex(key.getBytes("UTF-8")).substring(0, CHECKSUM_SIZE);
- @Override
- public String toString() {
- return getKey();
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Organization is not UTF-8 encoded: " + organization, e);
}
}
}
import java.net.UnknownHostException;
import static org.hamcrest.text.StringStartsWith.startsWith;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
public class ServerKeyGeneratorTest {
@Test
public void keyShouldHaveTenCharacters() {
- ServerKeyGenerator.ServerKey key = new ServerKeyGenerator.ServerKey("SonarSource", localhost);
- assertThat(key.getKey().length(), Is.is(10)); // first character is version + 9 characters for checksum
- assertThat(StringUtils.isBlank(key.getKey()), Is.is(false));
+ String key = new ServerKeyGenerator().toKey("SonarSource", localhost);
+ assertThat(key.length(), Is.is(10)); // first character is version + 9 characters for checksum
+ assertThat(StringUtils.isBlank(key), Is.is(false));
}
@Test
public void keyShouldStartWithVersion() {
- ServerKeyGenerator.ServerKey key = new ServerKeyGenerator.ServerKey("SonarSource", localhost);
- assertThat(key.getKey(), startsWith(ServerKeyGenerator.VERSION));
+ String key = new ServerKeyGenerator().toKey("SonarSource", localhost);
+ assertThat(key, startsWith(ServerKeyGenerator.VERSION));
}
@Test
- public void loopbackAddressesShouldNotBeValid() throws UnknownHostException {
- assertThat(new ServerKeyGenerator.ServerKey("SonarSource", InetAddress.getByName("127.0.0.1")).isValid(), Is.is(false));
+ public void loopbackAddressesShouldNotBeAccepted() throws UnknownHostException {
+ assertThat(new ServerKeyGenerator().isFixed(InetAddress.getByName("127.0.0.1")), Is.is(false));
}
@Test
- public void testEqualsAndHashCode() {
- ServerKeyGenerator.ServerKey key1 = new ServerKeyGenerator.ServerKey("Corp One", localhost);
- ServerKeyGenerator.ServerKey key2 = new ServerKeyGenerator.ServerKey("Corp Two", localhost);
- assertEquals(key1, key1);
- assertEquals(key1.hashCode(), key1.hashCode());
-
- assertThat(key1.equals(key2), Is.is(false));
- assertThat(key2.equals(key1), Is.is(false));
-
- assertThat(key1.equals("string"), Is.is(false));
+ public void publicAddressesNotBeAccepted() throws UnknownHostException {
+ assertThat(new ServerKeyGenerator().isFixed(InetAddress.getByName("sonarsource.com")), Is.is(true));
}
@Test
- public void shouldGenerateKey() {
- String key = new ServerKeyGenerator().generate("SonarSource");
- assertThat(StringUtils.isNotBlank(key), Is.is(true));
- }
-
- @Test
- public void organizationShouldBeMandatory() {
- assertNull(new ServerKeyGenerator().generate(null));
- assertNull(new ServerKeyGenerator().generate(""));
- assertNull(new ServerKeyGenerator().generate(" "));
+ public void shouldBeAddressOwner() throws UnknownHostException {
+ assertThat(new ServerKeyGenerator().isOwner(InetAddress.getByName("sonarsource.com")), Is.is(false));
+ assertThat(new ServerKeyGenerator().isOwner(InetAddress.getByName("localhost")), Is.is(true));
+ assertThat(new ServerKeyGenerator().isOwner(InetAddress.getByName("127.0.0.1")), Is.is(true));
}
@Test
public void keyShouldBeUniquePerOrganization() {
- ServerKeyGenerator generator = new ServerKeyGenerator();
- String k1 = generator.generate("Corp One");
- String k2 = generator.generate("Corp Two");
+ ServerKeyGenerator generator = new ServerKeyGenerator(true);
+ String k1 = generator.generate("Corp One", "http://localhost:9000");
+ String k2 = generator.generate("Corp Two", "http://localhost:9000");
assertThat(StringUtils.equals(k1, k2), Is.is(false));
}
@Test
public void keyShouldBeReproducible() {
- ServerKeyGenerator generator = new ServerKeyGenerator();
- String k1 = generator.generate("SonarSource");
- String k2 = generator.generate("SonarSource");
+ ServerKeyGenerator generator = new ServerKeyGenerator(true);
+ String k1 = generator.generate("SonarSource", "http://localhost:9000");
+ String k2 = generator.generate("SonarSource", "http://localhost:9000");
assertThat(StringUtils.equals(k1, k2), Is.is(true));
}
@Test
- public void shouldNotKeepPreviousKeyIfNotValid() {
- ServerKeyGenerator generator = new ServerKeyGenerator();
- String key = generator.generate("SonarSource", "unvalid");
- assertNotNull(key);
- assertThat(StringUtils.equals(key, "unvalid"), Is.is(false));
+ public void shouldExtractAddressFromUrl() {
+ assertThat(new ServerKeyGenerator().extractAddressFromUrl("https://localhost:9000").getHostAddress(), Is.is("127.0.0.1"));
+ assertThat(new ServerKeyGenerator().extractAddressFromUrl("http://sonarsource.com/sonar").getHostName(), Is.is("sonarsource.com"));
}
-
-
}