]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7984 WS api/server_id/generate 1217/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 6 Sep 2016 15:00:25 +0000 (17:00 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 7 Sep 2016 15:06:10 +0000 (17:06 +0200)
20 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdLoader.java
server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWs.java
server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWsModule.java
server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ShowAction.java
server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/generate-example.json [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/show-example.json
server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdGeneratorTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/serverid/ws/GenerateActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java
server/sonar-server/src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/GenerateRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/package-info.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-serverid.proto
sonar-ws/src/test/java/org/sonarqube/ws/client/serverid/GenerateRequestTest.java [new file with mode: 0644]

index 86d03a72efed7836e7b444f69d94e883428f162f..7439b6dd60dde8b5a08e77f85538667c3ec31bac 100644 (file)
@@ -28,12 +28,16 @@ import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.Objects;
 import java.util.regex.Pattern;
 import javax.annotation.CheckForNull;
 import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.StringUtils;
 import org.sonar.api.utils.log.Loggers;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
 public class ServerIdGenerator {
 
   private static final Pattern ORGANIZATION_PATTERN = Pattern.compile("[a-zA-Z0-9]+[a-zA-Z0-9 ]*");
@@ -56,21 +60,33 @@ public class ServerIdGenerator {
     this.acceptPrivateAddress = acceptPrivateAddress;
   }
 
-  @CheckForNull
-  public String generate(String organisationName, String ipAddress) {
-    String id = null;
-    String organisation = organisationName.trim();
+  public boolean validate(String organisationName, String ipAddress, String expectedServerId) {
+    String organization = organisationName.trim();
     String ip = ipAddress.trim();
-    if (StringUtils.isNotBlank(organisation) && StringUtils.isNotBlank(ip) && isValidOrganizationName(organisation)) {
-      InetAddress inetAddress = toValidAddress(ip);
-      if (inetAddress != null) {
-        id = toId(organisation, inetAddress);
-      }
+    if (isBlank(ip) || isBlank(organization) || !isValidOrganizationName(organization)) {
+      return false;
     }
-    return id;
+
+    InetAddress inetAddress = toValidAddress(ip);
+
+    return inetAddress != null
+      && Objects.equals(expectedServerId, toId(organization, inetAddress));
   }
 
-  boolean isValidOrganizationName(String organisation) {
+  public String generate(String organizationName, String ipAddress) {
+    String organization = organizationName.trim();
+    String ip = ipAddress.trim();
+    checkArgument(isNotBlank(organization), "Organization name must not be null or empty");
+    checkArgument(isValidOrganizationName(organization), "Organization name is invalid. Alpha numeric characters and space only are allowed. '%s' was provided.", organization);
+    checkArgument(isNotBlank(ip), "IP must not be null or empty");
+
+    InetAddress inetAddress = toValidAddress(ip);
+    checkArgument(inetAddress != null, "Invalid IP '%s'", ip);
+
+    return toId(organization, inetAddress);
+  }
+
+  static boolean isValidOrganizationName(String organisation) {
     return ORGANIZATION_PATTERN.matcher(organisation).matches();
   }
 
@@ -81,13 +97,14 @@ public class ServerIdGenerator {
     return acceptPrivateAddress || (!address.isLoopbackAddress() && !address.isLinkLocalAddress());
   }
 
-  String toId(String organisation, InetAddress address) {
+  static String toId(String organisation, InetAddress address) {
     String id = new StringBuilder().append(organisation).append("-").append(address.getHostAddress()).toString();
     return VERSION + DigestUtils.sha1Hex(id.getBytes(StandardCharsets.UTF_8)).substring(0, CHECKSUM_SIZE);
   }
 
+  @CheckForNull
   private InetAddress toValidAddress(String ipAddress) {
-    if (StringUtils.isNotBlank(ipAddress)) {
+    if (isNotBlank(ipAddress)) {
       List<InetAddress> validAddresses = getAvailableAddresses();
       try {
         InetAddress address = InetAddress.getByName(ipAddress);
index 37f9fd597287e51277c31dab57b9ed65ce93a5a5..6ca91d6e2c8fa2956ef6e7004c13cdf69088380e 100644 (file)
@@ -47,13 +47,10 @@ public class ServerIdLoader {
 
     String organisation = settings.getString(CoreProperties.ORGANISATION);
     String ipAddress = settings.getString(CoreProperties.SERVER_ID_IP_ADDRESS);
-    boolean validated;
-    if (organisation == null || ipAddress == null) {
-      validated = false;
-    } else {
-      String generatedId = idGenerator.generate(organisation, ipAddress);
-      validated = generatedId != null && generatedId.equals(rawId.get());
-    }
+    boolean validated = organisation != null
+      && ipAddress != null
+      && idGenerator.validate(organisation, ipAddress, rawId.get());
+
     return Optional.of(new ServerId(rawId.get(), validated));
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java
new file mode 100644 (file)
index 0000000..bfe0690
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.serverid.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.ServerIdGenerator;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.ServerId.GenerateWsResponse;
+import org.sonarqube.ws.client.serverid.GenerateRequest;
+
+import static org.sonar.api.CoreProperties.ORGANISATION;
+import static org.sonar.api.CoreProperties.PERMANENT_SERVER_ID;
+import static org.sonar.api.CoreProperties.SERVER_ID_IP_ADDRESS;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class GenerateAction implements ServerIdWsAction {
+  public static final String PARAM_ORGANIZATION = "organization";
+  public static final String PARAM_IP = "ip";
+
+  private static final Logger LOG = Loggers.get(GenerateAction.class);
+
+  private final UserSession userSession;
+  private final ServerIdGenerator generator;
+  private final DbClient dbClient;
+
+  public GenerateAction(UserSession userSession, ServerIdGenerator generator, DbClient dbClient) {
+    this.userSession = userSession;
+    this.generator = generator;
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("generate")
+      .setDescription("Generate a server id.<br/>" +
+        "Requires 'System Administer' permissions")
+      .setSince("6.1")
+      .setInternal(true)
+      .setPost(true)
+      .setResponseExample(getClass().getResource("generate-example.json"))
+      .setHandler(this);
+
+    action.createParam(PARAM_ORGANIZATION)
+      .setDescription("Organization name")
+      .setExampleValue("SonarSource")
+      .setRequired(true);
+
+    action.createParam(PARAM_IP)
+      .setDescription("IP address")
+      .setExampleValue("10.142.20.56")
+      .setRequired(true);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkPermission(SYSTEM_ADMIN);
+
+    DbSession dbSession = dbClient.openSession(true);
+    try {
+      writeProtobuf(doHandle(dbSession, toGenerateRequest(request)), request, response);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  private GenerateWsResponse doHandle(DbSession dbSession, GenerateRequest request) {
+    String serverId = generator.generate(request.getOrganization(), request.getIp());
+    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(PERMANENT_SERVER_ID).setValue(serverId));
+    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(ORGANISATION).setValue(request.getOrganization()));
+    dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(SERVER_ID_IP_ADDRESS).setValue(request.getIp()));
+    dbSession.commit();
+    LOG.info("Generated new server ID={}", serverId);
+
+    return GenerateWsResponse.newBuilder().setServerId(serverId).build();
+  }
+
+  private static GenerateRequest toGenerateRequest(Request request) {
+    return GenerateRequest.builder()
+      .setOrganization(request.mandatoryParam(PARAM_ORGANIZATION))
+      .setIp(request.mandatoryParam(PARAM_IP))
+      .build();
+  }
+
+}
index 0825f9f9dac9deacb1f8384936d8472b6675aa58..37aaef58342b44af6063f0d6bf5093dfb8822eaa 100644 (file)
@@ -32,7 +32,8 @@ public class ServerIdWs implements WebService {
   @Override
   public void define(Context context) {
     NewController controller = context.createController("api/server_id")
-      .setDescription("Get server id information and generate server id.");
+      .setDescription("Get server id information and generate server id.")
+      .setSince("6.1");
     for (ServerIdWsAction action : actions) {
       action.define(controller);
     }
index 2f30d5cbbb640f89a858afe23ad91b0b1020ffe5..d8ab4dd59aac3dd949da94855b460ba2db685f9b 100644 (file)
@@ -26,6 +26,7 @@ public class ServerIdWsModule extends Module {
   protected void configureModule() {
     add(
       ServerIdWs.class,
-      ShowAction.class);
+      ShowAction.class,
+      GenerateAction.class);
   }
 }
index 183f00eacca64f134008bbe1f9ab540bf8309096..e65bbcc22e15cc2036ebe4ec3df2dc84626f2307 100644 (file)
@@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableSet;
 import java.net.InetAddress;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
@@ -115,11 +114,9 @@ public class ShowAction implements ServerIdWsAction {
     return propertyDto != null ? Optional.of(propertyDto.getValue()) : Optional.empty();
   }
 
-  private boolean isValidServerId(String serverId, Optional<String> organisation, Optional<String> ip) {
-    if (organisation.isPresent() && ip.isPresent()) {
-      String generatedServerId = serverIdGenerator.generate(organisation.get(), ip.get());
-      return Objects.equals(generatedServerId, serverId);
-    }
-    return false;
+  private boolean isValidServerId(String serverId, Optional<String> organization, Optional<String> ip) {
+    return organization.isPresent()
+      && ip.isPresent()
+      && serverIdGenerator.validate(organization.get(), ip.get(), serverId);
   }
 }
index 0789b460c1ffbaf52e6554ed6b09887c815684ba..105d05c116adca634c1d75e21443ec6086634090 100644 (file)
@@ -55,12 +55,12 @@ import org.sonar.server.authentication.IdentityProviderRepository;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.measure.MeasureFilterEngine;
 import org.sonar.server.measure.MeasureFilterResult;
+import org.sonar.server.platform.PersistentSettings;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.platform.ServerIdGenerator;
 import org.sonar.server.platform.db.migrations.DatabaseMigrator;
 import org.sonar.server.platform.ws.UpgradesAction;
 import org.sonar.server.rule.RuleRepositories;
-import org.sonar.server.platform.PersistentSettings;
 import org.sonar.server.user.NewUserNotifier;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -274,7 +274,7 @@ public final class JRubyFacade {
   }
 
   public String generateRandomSecretKey() {
-    return  get(Settings.class).getEncryption().generateRandomSecretKey();
+    return get(Settings.class).getEncryption().generateRandomSecretKey();
   }
 
   public License parseLicense(String base64) {
@@ -361,7 +361,7 @@ public final class JRubyFacade {
     }
   }
 
-  public List<IdentityProvider> getIdentityProviders(){
+  public List<IdentityProvider> getIdentityProviders() {
     return get(IdentityProviderRepository.class).getAllEnabledAndSorted();
   }
 
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/generate-example.json b/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/generate-example.json
new file mode 100644 (file)
index 0000000..3ebe88f
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "serverId": "1818a1eefb26f9g"
+}
index 1f322a4cc571c5c1be49e1549f65584eaf61ca4a..bd00b32b7685c40bf506ae4d1606ba9882ad6893 100644 (file)
@@ -1,5 +1,5 @@
 {
-  "serverId": "server_id",
+  "serverId": "1818a1eefb26f9g",
   "organization": "home",
   "ip": "127.0.1",
   "validIpAdresses": [
index 72052685400c72cc5b6e4674086e80742cee5203..62f5105c54da6a4fbbadbfe9734281b1c8c69192 100644 (file)
@@ -23,14 +23,20 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import org.apache.commons.lang.StringUtils;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import static org.apache.commons.lang.StringUtils.isBlank;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class ServerIdGeneratorTest {
+  static InetAddress localhost;
 
-  private static InetAddress localhost;
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  ServerIdGenerator underTest = new ServerIdGenerator(true);
 
   @BeforeClass
   public static void init() throws UnknownHostException {
@@ -40,15 +46,50 @@ public class ServerIdGeneratorTest {
   @Test
   public void shouldNotGenerateIdIfBlankParams() {
     ServerIdGenerator generator = new ServerIdGenerator(true);
-    assertThat(generator.generate("  ", "127.0.0.1")).isNull();
-    assertThat(generator.generate("SonarSource", "   ")).isNull();
+    assertThat(generator.validate("  ", "127.0.0.1", "191e806623bb0c2")).isFalse();
+    assertThat(generator.validate("SonarSource", "   ", "191e806623bb0c2")).isFalse();
   }
 
   @Test
   public void organizationShouldRespectPattern() {
     ServerIdGenerator generator = new ServerIdGenerator(true);
-    assertThat(generator.generate("SonarSource", "127.0.0.1")).isNotNull();
-    assertThat(generator.generate("SonarSource$", "127.0.0.1")).isNull();
+    assertThat(generator.generate("SonarSource", "127.0.0.1")).isEqualTo("191e806623bb0c2");
+    assertThat(generator.validate("SonarSource", "127.0.0.1", "191e806623bb0c2")).isTrue();
+    assertThat(generator.validate("SonarSource$", "127.0.0.1", "191e806623bb0c2")).isFalse();
+  }
+
+  @Test
+  public void fail_if_organization_does_not_respect_pattern() {
+    assertThat(underTest.generate("SonarSource", "127.0.0.1")).isNotEmpty();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Organization name is invalid. Alpha numeric characters and space only are allowed. 'SonarSource$' was provided.");
+
+    underTest.generate("SonarSource$", "127.0.0.1");
+  }
+
+  @Test
+  public void fail_if_organization_is_blank() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Organization name must not be null or empty");
+
+    underTest.generate("   ", "127.0.0.1");
+  }
+
+  @Test
+  public void fail_if_ip_blank() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("IP must not be null or empty");
+
+    underTest.generate("SonarSource", "     ");
+  }
+
+  @Test
+  public void fail_if_ip_is_unknown() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid IP '50.154.42.42'");
+
+    underTest.generate("SonarSource", "50.154.42.42");
   }
 
   @Test
index c056a1c502f9472d8606b03f252ba4e67c86c37e..89b87c8367d60640cb9d97faff8ae555f55fd00e 100644 (file)
@@ -56,11 +56,11 @@ public class ServerIdLoaderTest {
     settings.setProperty(CoreProperties.PERMANENT_SERVER_ID, AN_ID);
     settings.setProperty(CoreProperties.ORGANISATION, AN_ORGANISATION);
     settings.setProperty(CoreProperties.SERVER_ID_IP_ADDRESS, AN_IP);
-    when(idGenerator.generate(AN_ORGANISATION, AN_IP)).thenReturn(AN_ID);
+    when(idGenerator.validate(AN_ORGANISATION, AN_IP, AN_ID)).thenReturn(true);
 
     Optional<ServerId> serverIdOpt = underTest.get();
     verifyServerId(serverIdOpt.get(), AN_ID, true);
-    verify(idGenerator).generate(AN_ORGANISATION, AN_IP);
+    verify(idGenerator).validate(AN_ORGANISATION, AN_IP, AN_ID);
   }
 
   @Test
@@ -104,7 +104,7 @@ public class ServerIdLoaderTest {
     Optional<ServerId> serverIdOpt = underTest.get();
 
     verifyServerId(serverIdOpt.get(), AN_ID, false);
-    verify(idGenerator).generate(AN_ORGANISATION, AN_IP);
+    verify(idGenerator).validate(AN_ORGANISATION, AN_IP, AN_ID);
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/GenerateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/GenerateActionTest.java
new file mode 100644 (file)
index 0000000..ceb7fa4
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.serverid.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.platform.ServerIdGenerator;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.ServerId.GenerateWsResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.CoreProperties.ORGANISATION;
+import static org.sonar.api.CoreProperties.PERMANENT_SERVER_ID;
+import static org.sonar.api.CoreProperties.SERVER_ID_IP_ADDRESS;
+import static org.sonar.server.serverid.ws.GenerateAction.PARAM_IP;
+import static org.sonar.server.serverid.ws.GenerateAction.PARAM_ORGANIZATION;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.MediaTypes.PROTOBUF;
+
+public class GenerateActionTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+  @Rule
+  public LogTester log = new LogTester();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  DbClient dbClient = db.getDbClient();
+
+  ServerIdGenerator generator = mock(ServerIdGenerator.class);
+
+  GenerateAction underTest = new GenerateAction(userSession, generator, dbClient);
+
+  WsActionTester ws = new WsActionTester(underTest);
+
+  @Test
+  public void persist_settings() {
+    when(generator.generate("SonarSource", "10.51.42.255")).thenReturn("server_id");
+
+    GenerateWsResponse result = call("SonarSource", "10.51.42.255");
+
+    assertThat(result.getServerId()).isEqualTo("server_id");
+    assertGlobalSetting(ORGANISATION, "SonarSource");
+    assertGlobalSetting(SERVER_ID_IP_ADDRESS, "10.51.42.255");
+    assertGlobalSetting(PERMANENT_SERVER_ID, "server_id");
+  }
+
+  @Test
+  public void json_example() {
+    when(generator.generate("SonarSource", "127.0.0.1")).thenReturn("1818a1eefb26f9g");
+
+    String result = ws.newRequest()
+      .setParam(PARAM_ORGANIZATION, "SonarSource")
+      .setParam(PARAM_IP, "127.0.0.1")
+      .execute().getInput();
+
+    assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
+  }
+
+  @Test
+  public void log_message_when_id_generated() {
+    when(generator.generate("SonarSource", "127.0.0.1")).thenReturn("server_id");
+
+    call("SonarSource", "127.0.0.1");
+
+    assertThat(log.logs(LoggerLevel.INFO)).contains("Generated new server ID=" + "server_id");
+  }
+
+  @Test
+  public void definition() {
+    WebService.Action definition = ws.getDef();
+
+    assertThat(definition.key()).isEqualTo("generate");
+    assertThat(definition.since()).isEqualTo("6.1");
+    assertThat(definition.isInternal()).isTrue();
+    assertThat(definition.isPost()).isTrue();
+    assertThat(definition.responseExampleAsString()).isNotEmpty();
+    assertThat(definition.params()).hasSize(2);
+  }
+
+  @Test
+  public void fail_if_insufficient_permission() {
+    userSession.setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+
+    expectedException.expect(ForbiddenException.class);
+
+    call("SonarSource", "127.0.0.1");
+  }
+
+  @Test
+  public void fail_if_no_organization() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    call(null, "127.0.0.1");
+  }
+
+  @Test
+  public void fail_if_empty_organization() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    call("", "127.0.0.1");
+  }
+
+  @Test
+  public void fail_if_no_ip() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    call("SonarSource", null);
+  }
+
+  @Test
+  public void fail_if_empty_ip() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    call("SonarSource", "");
+  }
+
+  private void assertGlobalSetting(String key, String value) {
+    PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key);
+
+    assertThat(result)
+      .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId)
+      .containsExactly(key, value, null);
+  }
+
+  private GenerateWsResponse call(@Nullable String organization, @Nullable String ip) {
+    TestRequest request = ws.newRequest()
+      .setMethod("POST")
+      .setMediaType(PROTOBUF);
+
+    if (organization != null) {
+      request.setParam(PARAM_ORGANIZATION, organization);
+    }
+
+    if (ip != null) {
+      request.setParam(PARAM_IP, ip);
+    }
+
+    try {
+      return GenerateWsResponse.parseFrom(request.execute().getInputStream());
+    } catch (IOException e) {
+      throw Throwables.propagate(e);
+    }
+  }
+}
index 7c0bf9c26dd2be88b8d2474012b9a44beabe48c6..e989bbd45f70addf43d2cf46a9cd9b6cdd86b1c9 100644 (file)
@@ -30,6 +30,6 @@ public class ServerIdWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ServerIdWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(2 + 2);
+    assertThat(container.size()).isEqualTo(3 + 2);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsTest.java
new file mode 100644 (file)
index 0000000..8bcd99a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.serverid.ws;
+
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.server.platform.ServerIdGenerator;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.WsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ServerIdWsTest {
+
+  WsTester ws = new WsTester(new ServerIdWs(new ShowAction(mock(UserSession.class), mock(ServerIdGenerator.class), mock(DbClient.class))));
+  WebService.Controller underTest = ws.controller("api/server_id");
+
+  @Test
+  public void definition() {
+    assertThat(underTest.path()).isEqualTo("api/server_id");
+    assertThat(underTest.since()).isEqualTo("6.1");
+    assertThat(underTest.description()).isNotEmpty();
+  }
+}
index 2a7dd64321b908fcdd8d62fa6feed15148d726c2..fdbc861ae02acb4060534b50a9a6d720fbb91a92 100644 (file)
@@ -70,13 +70,13 @@ public class ShowActionTest {
   @Test
   public void return_server_id_info() throws Exception {
     setUserAsSystemAdmin();
-    when(generator.generate("home", "127.0.1")).thenReturn("server_id");
+    when(generator.validate("home", "127.0.1", "1818a1eefb26f9g")).thenReturn(true);
     setAvailableIpAdresses("192.168.1.1", "127.0.1");
-    insertConfiguration("server_id", "home", "127.0.1");
+    insertConfiguration("1818a1eefb26f9g", "home", "127.0.1");
 
     ShowWsResponse response = executeRequest();
 
-    assertThat(response.getServerId()).isEqualTo("server_id");
+    assertThat(response.getServerId()).isEqualTo("1818a1eefb26f9g");
     assertThat(response.getOrganization()).isEqualTo("home");
     assertThat(response.getIp()).isEqualTo("127.0.1");
     assertThat(response.getValidIpAdressesList()).containsOnly("192.168.1.1", "127.0.1");
@@ -86,7 +86,7 @@ public class ShowActionTest {
   @Test
   public void return_invalid_server_id() throws Exception {
     setUserAsSystemAdmin();
-    when(generator.generate("home", "127.0.1")).thenReturn("server_id");
+    when(generator.validate("home", "127.0.1", "1818a1eefb26f9g")).thenReturn(true);
     insertConfiguration("invalid", null, null);
 
     ShowWsResponse response = executeRequest();
@@ -161,9 +161,9 @@ public class ShowActionTest {
   @Test
   public void test_example_json_response() {
     setUserAsSystemAdmin();
-    when(generator.generate("home", "127.0.1")).thenReturn("server_id");
+    when(generator.validate("home", "127.0.1", "1818a1eefb26f9g")).thenReturn(true);
     setAvailableIpAdresses("192.168.1.1", "127.0.1");
-    insertConfiguration("server_id", "home", "127.0.1");
+    insertConfiguration("1818a1eefb26f9g", "home", "127.0.1");
 
     String result = ws.newRequest()
       .setMediaType(JSON)
index 4fdd4840b0c5a262d4e872e83bf856d8f850138e..269e6913613e3c057f708883a3bb64f5a4f8bb0b 100644 (file)
@@ -117,7 +117,7 @@ public class ThreadLocalSettingsTest {
 
   @Test
   public void encryption_secret_key_is_undefined_by_default() {
-    underTest = create(ImmutableMap.of("foo", "bar"));
+    underTest = create(ImmutableMap.of("foo", "bar", "sonar.secretKeyPath", "unknown/path/to/sonar-secret.txt"));
 
     assertThat(underTest.getEncryption().hasSecretKey()).isFalse();
   }
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/GenerateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/GenerateRequest.java
new file mode 100644 (file)
index 0000000..57b29d6
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.sonarqube.ws.client.serverid;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class GenerateRequest {
+  private final String organization;
+  private final String ip;
+
+  public GenerateRequest(Builder builder) {
+    this.organization = builder.organization;
+    this.ip = builder.ip;
+  }
+
+  public String getOrganization() {
+    return organization;
+  }
+
+  public String getIp() {
+    return ip;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String organization;
+    private String ip;
+
+    private Builder() {
+      // enforce static constructor
+    }
+
+    public Builder setOrganization(String organization) {
+      this.organization = organization;
+      return this;
+    }
+
+    public Builder setIp(String ip) {
+      this.ip = ip;
+      return this;
+    }
+
+    public GenerateRequest build() {
+      checkArgument(organization != null && !organization.isEmpty(), "Organization must not be null or empty");
+      checkArgument(ip != null && !ip.isEmpty(), "IP must not be null or empty");
+      return new GenerateRequest(this);
+    }
+  }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/package-info.java
new file mode 100644 (file)
index 0000000..2ea5563
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+
+
+
+@ParametersAreNonnullByDefault
+package org.sonarqube.ws.client.serverid;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 18a2a813ef6c163174fa30e868c75977add75de9..af02b223d868949eccc8ea0288df86a43a970016 100644 (file)
@@ -33,6 +33,7 @@ message ShowWsResponse {
   optional bool invalidServerId = 5;
 }
 
-
-
-
+// Response of POST api/server_id/generate
+message GenerateWsResponse {
+  optional string serverId = 1;
+}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/serverid/GenerateRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/serverid/GenerateRequestTest.java
new file mode 100644 (file)
index 0000000..ecbf258
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.sonarqube.ws.client.serverid;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class GenerateRequestTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  GenerateRequest.Builder underTest = GenerateRequest.builder();
+
+  @Test
+  public void fail_if_null_organization() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Organization must not be null or empty");
+
+    underTest.setIp("127.0.0.1").setOrganization(null).build();
+  }
+
+  @Test
+  public void fail_if_empty_organization() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Organization must not be null or empty");
+
+    underTest.setIp("127.0.0.1").setOrganization("").build();
+  }
+
+  @Test
+  public void fail_if_null_ip() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("IP must not be null or empty");
+
+    underTest.setOrganization("SonarSource").setIp(null).build();
+  }
+
+  @Test
+  public void fail_if_empty_ip() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("IP must not be null or empty");
+
+    underTest.setOrganization("SonarSource").setIp("").build();
+  }
+}