From b6320701b6a540f22bd27e61ef06d9607ba3105f Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Thu, 1 Sep 2016 14:34:29 +0200 Subject: [PATCH] SONAR-8055 Create /api/emails/update_configuration WS to update email settings --- .../org/sonar/server/email/ws/EmailsWs.java | 42 ++++ .../sonar/server/email/ws/EmailsWsAction.java | 26 +++ .../sonar/server/email/ws/EmailsWsModule.java | 31 +++ .../email/ws/UpdateConfigurationAction.java | 142 ++++++++++++++ .../sonar/server/email/ws/package-info.java | 23 +++ .../platformlevel/PlatformLevel4.java | 2 + .../server/email/ws/EmailsWsModuleTest.java | 34 ++++ .../ws/UpdateConfigurationActionTest.java | 182 ++++++++++++++++++ 8 files changed, 482 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWs.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsModule.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/email/ws/UpdateConfigurationAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/email/ws/package-info.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/email/ws/EmailsWsModuleTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/email/ws/UpdateConfigurationActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWs.java b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWs.java new file mode 100644 index 00000000000..a33cac5c392 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWs.java @@ -0,0 +1,42 @@ +/* + * 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.email.ws; + +import org.sonar.api.server.ws.WebService; + +public class EmailsWs implements WebService { + + private final EmailsWsAction[] actions; + + public EmailsWs(EmailsWsAction... actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/emails") + .setDescription("Manage emails") + .setSince("6.1"); + for (EmailsWsAction action : actions) { + action.define(controller); + } + controller.done(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsAction.java new file mode 100644 index 00000000000..bbfd9b2cafe --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsAction.java @@ -0,0 +1,26 @@ +/* + * 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.email.ws; + +import org.sonar.server.ws.WsAction; + +public interface EmailsWsAction extends WsAction { + // marker interface +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsModule.java new file mode 100644 index 00000000000..9db36399e97 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/email/ws/EmailsWsModule.java @@ -0,0 +1,31 @@ +/* + * 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.email.ws; + +import org.sonar.core.platform.Module; + +public class EmailsWsModule extends Module { + @Override + protected void configureModule() { + add( + EmailsWs.class, + UpdateConfigurationAction.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/email/ws/UpdateConfigurationAction.java b/server/sonar-server/src/main/java/org/sonar/server/email/ws/UpdateConfigurationAction.java new file mode 100644 index 00000000000..524efb8409a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/email/ws/UpdateConfigurationAction.java @@ -0,0 +1,142 @@ +/* + * 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.email.ws; + +import javax.annotation.Nullable; +import org.sonar.api.config.Settings; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.property.PropertyDto; +import org.sonar.server.user.UserSession; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.sonar.api.config.EmailSettings.FROM; +import static org.sonar.api.config.EmailSettings.FROM_DEFAULT; +import static org.sonar.api.config.EmailSettings.PREFIX; +import static org.sonar.api.config.EmailSettings.PREFIX_DEFAULT; +import static org.sonar.api.config.EmailSettings.SMTP_HOST; +import static org.sonar.api.config.EmailSettings.SMTP_PASSWORD; +import static org.sonar.api.config.EmailSettings.SMTP_PORT; +import static org.sonar.api.config.EmailSettings.SMTP_PORT_DEFAULT; +import static org.sonar.api.config.EmailSettings.SMTP_SECURE_CONNECTION; +import static org.sonar.api.config.EmailSettings.SMTP_USERNAME; + +public class UpdateConfigurationAction implements EmailsWsAction { + + private static final String PARAM_HOST = "host"; + private static final String PARAM_PORT = "port"; + private static final String PARAM_SECURE = "secure"; + private static final String PARAM_USERNAME = "username"; + private static final String PARAM_PASSWORD = "password"; + private static final String PARAM_FROM = "from"; + private static final String PARAM_PREFIX = "prefix"; + + private static final String SSL_VALUE = "ssl"; + private static final String STARTTLS_VALUE = "starttls"; + + private final DbClient dbClient; + private final UserSession userSession; + + // TODO remove this when EmailSettings won't use Settings in memory to get settings + private final Settings settings; + + public UpdateConfigurationAction(DbClient dbClient, UserSession userSession, Settings settings) { + this.dbClient = dbClient; + this.userSession = userSession; + this.settings = settings; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("update_configuration") + .setDescription("Update email configuration
" + + "If a parameter is not set, it will be reset
" + + "Requires 'Administer System' permission.") + .setSince("6.1") + .setInternal(true) + .setPost(true) + .setHandler(this); + + action.createParam(PARAM_HOST) + .setDescription("SMTP host.") + .setExampleValue("smtp.gmail.com"); + + action.createParam(PARAM_PORT) + .setDescription("Port number to connect with SMTP server.") + .setExampleValue(SMTP_PORT_DEFAULT); + + action.createParam(PARAM_SECURE) + .setDescription("Whether to use secure connection and its type.") + .setPossibleValues(SSL_VALUE, STARTTLS_VALUE) + .setExampleValue(SSL_VALUE); + + action.createParam(PARAM_USERNAME) + .setDescription("Username to use with authenticated SMTP.") + .setExampleValue("my_username"); + + action.createParam(PARAM_PASSWORD) + .setDescription("Username to use with authenticated SMTP.") + .setExampleValue("my_password"); + + action.createParam(PARAM_FROM) + .setDescription("Emails will come from this address. For example - \"noreply@sonarsource.com\". Note that server may ignore this setting.") + .setExampleValue(FROM_DEFAULT); + + action.createParam(PARAM_PREFIX) + .setDescription("Prefix will be prepended to all outgoing email subjects.") + .setExampleValue(PREFIX_DEFAULT); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN); + + DbSession dbSession = dbClient.openSession(false); + try { + save(dbSession, SMTP_HOST, request.param(PARAM_HOST), PARAM_HOST); + save(dbSession, SMTP_PORT, request.hasParam(PARAM_PORT) ? Integer.toString(request.paramAsInt(PARAM_PORT)) : null, PARAM_PORT); + save(dbSession, SMTP_SECURE_CONNECTION, request.param(PARAM_SECURE), PARAM_SECURE); + save(dbSession, SMTP_USERNAME, request.param(PARAM_USERNAME), PARAM_USERNAME); + save(dbSession, SMTP_PASSWORD, request.param(PARAM_PASSWORD), PARAM_PASSWORD); + save(dbSession, FROM, request.param(PARAM_FROM), PARAM_FROM); + save(dbSession, PREFIX, request.param(PARAM_PREFIX), PARAM_PREFIX); + dbSession.commit(); + } finally { + dbClient.closeSession(dbSession); + } + response.noContent(); + } + + private void save(DbSession dbSession, String settingKey, @Nullable String value, String requestKey) { + checkArgument(value == null || !value.isEmpty(), "Parameter '%s' cannot have an empty value", requestKey); + settings.setProperty(settingKey, value); + if (value != null) { + dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(settingKey).setValue(value)); + } else { + dbClient.propertiesDao().deleteGlobalProperty(settingKey, dbSession); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/email/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/email/ws/package-info.java new file mode 100644 index 00000000000..c447b1dd035 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/email/ws/package-info.java @@ -0,0 +1,23 @@ +/* + * 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.sonar.server.email.ws; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index b2a65af5969..1e7b008bc60 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -84,6 +84,7 @@ import org.sonar.server.debt.DebtRulesXMLImporter; import org.sonar.server.duplication.ws.DuplicationsJsonWriter; import org.sonar.server.duplication.ws.DuplicationsParser; import org.sonar.server.duplication.ws.DuplicationsWs; +import org.sonar.server.email.ws.EmailsWsModule; import org.sonar.server.es.IndexCreator; import org.sonar.server.es.IndexDefinitions; import org.sonar.server.event.NewAlerts; @@ -572,6 +573,7 @@ public class PlatformLevel4 extends PlatformLevel { NotificationService.class, NotificationCenter.class, DefaultNotificationManager.class, + EmailsWsModule.class, // Tests TestsWs.class, diff --git a/server/sonar-server/src/test/java/org/sonar/server/email/ws/EmailsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/email/ws/EmailsWsModuleTest.java new file mode 100644 index 00000000000..e42ea79fe68 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/email/ws/EmailsWsModuleTest.java @@ -0,0 +1,34 @@ +/* + * 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.email.ws; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmailsWsModuleTest { + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + new EmailsWsModule().configure(container); + assertThat(container.size()).isEqualTo(2 + 2); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/email/ws/UpdateConfigurationActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/email/ws/UpdateConfigurationActionTest.java new file mode 100644 index 00000000000..899cc95384e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/email/ws/UpdateConfigurationActionTest.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.email.ws; + +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.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.property.PropertyDbTester; +import org.sonar.db.property.PropertyDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto; + +public class UpdateConfigurationActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + DbClient dbClient = db.getDbClient(); + PropertyDbTester propertyDb = new PropertyDbTester(db); + org.sonar.api.config.Settings settings = new org.sonar.api.config.Settings(); + + WsActionTester ws = new WsActionTester(new UpdateConfigurationAction(dbClient, userSession, settings)); + + @Test + public void update_email_settings() throws Exception { + setUserAsSystemAdmin(); + + executeRequest("smtp.gmail.com", 25, "starttls", "john", "12345", "noreply@email.com", "[EMAIL]"); + + assertSetting("email.smtp_host.secured", "smtp.gmail.com"); + assertSetting("email.smtp_port.secured", "25"); + assertSetting("email.smtp_secure_connection.secured", "starttls"); + assertSetting("email.smtp_username.secured", "john"); + assertSetting("email.smtp_password.secured", "12345"); + assertSetting("email.from", "noreply@email.com"); + assertSetting("email.prefix", "[EMAIL]"); + } + + @Test + public void does_not_save_settings_when_no_value_sent() throws Exception { + setUserAsSystemAdmin(); + + executeRequest(null, null, null, null, null, null, null); + + assertThat(db.countRowsOfTable(db.getSession(), "properties")).isZero(); + } + + @Test + public void remove_existing_settings_when_no_value_sent() throws Exception { + setUserAsSystemAdmin(); + addSetting("email.smtp_host.secured", "smtp.gmail.com"); + addSetting("email.smtp_port.secured", "25"); + addSetting("email.smtp_secure_connection.secured", "starttls"); + addSetting("email.smtp_username.secured", "john"); + addSetting("email.smtp_password.secured", "12345"); + addSetting("email.from", "noreply@email.com"); + addSetting("email.prefix", "[EMAIL]"); + + executeRequest(null, null, null, null, null, null, null); + + assertThat(db.countRowsOfTable(db.getSession(), "properties")).isZero(); + } + + @Test + public void fail_when_secure_param_is_invalid() { + setUserAsSystemAdmin(); + + expectedException.expect(IllegalArgumentException.class); + + executeRequest(null, null, "unknown", null, null, null, null); + } + + @Test + public void fail_when_insufficient_privileges() { + userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + expectedException.expect(ForbiddenException.class); + + ws.newRequest().execute(); + } + + @Test + public void fail_when_parameter_is_empty() { + setUserAsSystemAdmin(); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Parameter 'host' cannot have an empty value"); + + executeRequest("", null, null, null, null, null, null); + } + + @Test + public void test_ws_definition() { + WebService.Action action = ws.getDef(); + assertThat(action).isNotNull(); + assertThat(action.isInternal()).isTrue(); + assertThat(action.isPost()).isTrue(); + assertThat(action.responseExampleAsString()).isNull(); + assertThat(action.params()).hasSize(7); + } + + private void assertSetting(String key, String value) { + PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key); + + assertThat(result) + .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId) + .containsExactly(key, value, null); + + assertThat(settings.getString(key)).isEqualTo(value); + } + + private void executeRequest(@Nullable String host, @Nullable Integer port, @Nullable String secure, + @Nullable String username, @Nullable String password, @Nullable String from, @Nullable String prefix) { + TestRequest request = ws.newRequest(); + if (host != null) { + request.setParam("host", host); + } + if (port != null) { + request.setParam("port", Integer.toString(port)); + } + if (secure != null) { + request.setParam("secure", secure); + } + if (username != null) { + request.setParam("username", username); + } + if (password != null) { + request.setParam("password", password); + } + if (from != null) { + request.setParam("from", from); + } + if (prefix != null) { + request.setParam("prefix", prefix); + } + request.execute(); + } + + private void addSetting(String key, String value) { + propertyDb.insertProperty(newGlobalPropertyDto(key, value)); + settings.setProperty(key, value); + } + + private void setUserAsSystemAdmin() { + userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); + } + +} -- 2.39.5