--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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<br/>" +
+ "If a parameter is not set, it will be reset<br>" +
+ "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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
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;
NotificationService.class,
NotificationCenter.class,
DefaultNotificationManager.class,
+ EmailsWsModule.class,
// Tests
TestsWs.class,
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}