From 7ef29c10d2efaaa9e092c90b1ac64e37f8dd056c Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Tue, 6 Sep 2016 17:00:25 +0200 Subject: [PATCH] SONAR-7984 WS api/server_id/generate --- .../server/platform/ServerIdGenerator.java | 45 +++-- .../sonar/server/platform/ServerIdLoader.java | 11 +- .../server/serverid/ws/GenerateAction.java | 110 +++++++++++ .../sonar/server/serverid/ws/ServerIdWs.java | 3 +- .../server/serverid/ws/ServerIdWsModule.java | 3 +- .../sonar/server/serverid/ws/ShowAction.java | 11 +- .../java/org/sonar/server/ui/JRubyFacade.java | 6 +- .../server/serverid/ws/generate-example.json | 3 + .../server/serverid/ws/show-example.json | 2 +- .../platform/ServerIdGeneratorTest.java | 51 ++++- .../server/platform/ServerIdLoaderTest.java | 6 +- .../serverid/ws/GenerateActionTest.java | 182 ++++++++++++++++++ .../serverid/ws/ServerIdWsModuleTest.java | 2 +- .../server/serverid/ws/ServerIdWsTest.java | 44 +++++ .../server/serverid/ws/ShowActionTest.java | 12 +- .../setting/ThreadLocalSettingsTest.java | 2 +- .../ws/client/serverid/GenerateRequest.java | 70 +++++++ .../ws/client/serverid/package-info.java | 27 +++ sonar-ws/src/main/protobuf/ws-serverid.proto | 7 +- .../client/serverid/GenerateRequestTest.java | 65 +++++++ 20 files changed, 609 insertions(+), 53 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/generate-example.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/serverid/ws/GenerateActionTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/GenerateRequest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/package-info.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/serverid/GenerateRequestTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java index 86d03a72efe..7439b6dd60d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdGenerator.java @@ -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 validAddresses = getAvailableAddresses(); try { InetAddress address = InetAddress.getByName(ipAddress); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdLoader.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdLoader.java index 37f9fd59728..6ca91d6e2c8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerIdLoader.java @@ -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 index 00000000000..bfe0690f728 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/GenerateAction.java @@ -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.
" + + "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(); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWs.java b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWs.java index 0825f9f9dac..37aaef58342 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWs.java @@ -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); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWsModule.java index 2f30d5cbbb6..d8ab4dd59aa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ServerIdWsModule.java @@ -26,6 +26,7 @@ public class ServerIdWsModule extends Module { protected void configureModule() { add( ServerIdWs.class, - ShowAction.class); + ShowAction.class, + GenerateAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ShowAction.java index 183f00eacca..e65bbcc22e1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/serverid/ws/ShowAction.java @@ -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 organisation, Optional 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 organization, Optional ip) { + return organization.isPresent() + && ip.isPresent() + && serverIdGenerator.validate(organization.get(), ip.get(), serverId); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 0789b460c1f..105d05c116a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -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 getIdentityProviders(){ + public List 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 index 00000000000..3ebe88f1542 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/generate-example.json @@ -0,0 +1,3 @@ +{ + "serverId": "1818a1eefb26f9g" +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/show-example.json b/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/show-example.json index 1f322a4cc57..bd00b32b768 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/show-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/serverid/ws/show-example.json @@ -1,5 +1,5 @@ { - "serverId": "server_id", + "serverId": "1818a1eefb26f9g", "organization": "home", "ip": "127.0.1", "validIpAdresses": [ diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdGeneratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdGeneratorTest.java index 72052685400..62f5105c54d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdGeneratorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdGeneratorTest.java @@ -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 diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdLoaderTest.java index c056a1c502f..89b87c8367d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdLoaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerIdLoaderTest.java @@ -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 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 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 index 00000000000..ceb7fa4bd70 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/GenerateActionTest.java @@ -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); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsModuleTest.java index 7c0bf9c26dd..e989bbd45f7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsModuleTest.java @@ -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 index 00000000000..8bcd99a484a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ServerIdWsTest.java @@ -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(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java index 2a7dd64321b..fdbc861ae02 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java @@ -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) diff --git a/server/sonar-server/src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java b/server/sonar-server/src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java index 4fdd4840b0c..269e6913613 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java @@ -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 index 00000000000..57b29d62b15 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/GenerateRequest.java @@ -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 index 00000000000..2ea55636428 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/serverid/package-info.java @@ -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; + diff --git a/sonar-ws/src/main/protobuf/ws-serverid.proto b/sonar-ws/src/main/protobuf/ws-serverid.proto index 18a2a813ef6..af02b223d86 100644 --- a/sonar-ws/src/main/protobuf/ws-serverid.proto +++ b/sonar-ws/src/main/protobuf/ws-serverid.proto @@ -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 index 00000000000..ecbf2588ae3 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/serverid/GenerateRequestTest.java @@ -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(); + } +} -- 2.39.5