--- /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.settings.ws;
+
+import java.util.Optional;
+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.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.KeyExamples;
+import org.sonarqube.ws.client.setting.SetRequest;
+
+public class SetAction implements SettingsWsAction {
+ private final DbClient dbClient;
+ private final ComponentFinder componentFinder;
+ private final UserSession userSession;
+
+ public SetAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.componentFinder = componentFinder;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("set")
+ .setDescription("Update a setting value.<br>" +
+ "Either '%s' or '%s' can be provided, not both.<br> " +
+ "Requires one of the following permissions: " +
+ "<ul>" +
+ "<li>'Administer System'</li>" +
+ "<li>'Administer' rights on the specified component</li>" +
+ "</ul>", "componentId", "componentKey")
+ .setSince("6.1")
+ .setPost(true)
+ .setHandler(this);
+
+ action.createParam("key")
+ .setDescription("Setting key")
+ .setExampleValue("sonar.links.scm")
+ .setRequired(true);
+
+ action.createParam("value")
+ .setDescription("Setting value. To reset a value, please use the reset web service.")
+ .setExampleValue("git@github.com:SonarSource/sonarqube.git")
+ .setRequired(true);
+
+ action.createParam("componentId")
+ .setDescription("Component id")
+ .setExampleValue(Uuids.UUID_EXAMPLE_01);
+
+ action.createParam("componentKey")
+ .setDescription("Component key")
+ .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ DbSession dbSession = dbClient.openSession(false);
+ try {
+ SetRequest setRequest = toWsRequest(request);
+ Optional<ComponentDto> component = searchComponent(dbSession, setRequest);
+ checkPermissions(component);
+
+ dbClient.propertiesDao().insertProperty(dbSession, toProperty(setRequest, component));
+ dbSession.commit();
+ } finally {
+ dbClient.closeSession(dbSession);
+ }
+
+ response.noContent();
+ }
+
+ private void checkPermissions(Optional<ComponentDto> component) {
+ if (component.isPresent()) {
+ userSession.checkComponentUuidPermission(UserRole.ADMIN, component.get().uuid());
+ } else {
+ userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN);
+ }
+ }
+
+ private static SetRequest toWsRequest(Request request) {
+ return SetRequest.builder()
+ .setKey(request.mandatoryParam("key"))
+ .setValue(request.mandatoryParam("value"))
+ .setComponentId(request.param("componentId"))
+ .setComponentKey(request.param("componentKey"))
+ .build();
+ }
+
+ private Optional<ComponentDto> searchComponent(DbSession dbSession, SetRequest request) {
+ if (request.getComponentId() == null && request.getComponentKey() == null) {
+ return Optional.empty();
+ }
+
+ ComponentDto project = componentFinder.getByUuidOrKey(dbSession, request.getComponentId(), request.getComponentKey(), ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY);
+
+ return Optional.of(project);
+ }
+
+ private static PropertyDto toProperty(SetRequest request, Optional<ComponentDto> component) {
+ PropertyDto property = new PropertyDto()
+ .setKey(request.getKey())
+ .setValue(request.getValue());
+
+ if (component.isPresent()) {
+ property.setResourceId(component.get().getId());
+ }
+
+ return property;
+ }
+}
protected void configureModule() {
add(
SettingsWs.class,
+ SetAction.class,
ListDefinitionsAction.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.settings.ws;
+
+import java.net.HttpURLConnection;
+import java.util.List;
+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.server.ws.WebService.Param;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.db.property.PropertyQuery;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.db.property.PropertyTesting.newGlobalProperty;
+import static org.sonar.db.property.PropertyTesting.newProjectProperty;
+
+public class SetActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone()
+ .setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ PropertyDbTester propertyDb = new PropertyDbTester(db);
+ ComponentDbTester componentDb = new ComponentDbTester(db);
+ DbClient dbClient = db.getDbClient();
+ DbSession dbSession = db.getSession();
+ ComponentFinder componentFinder = new ComponentFinder(dbClient);
+
+ SetAction underTest = new SetAction(dbClient, componentFinder, userSession);
+
+ WsActionTester ws = new WsActionTester(underTest);
+
+ @Test
+ public void empty_204_response() {
+ TestResponse result = ws.newRequest()
+ .setParam("key", "my.key")
+ .setParam("value", "my value")
+ .execute();
+
+ assertThat(result.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
+ assertThat(result.getInput()).isEmpty();
+ }
+
+ @Test
+ public void persist_new_global_property() {
+ callForGlobalProperty("my.key", "my value");
+
+ assertGlobalProperty("my.key", "my value");
+ }
+
+ @Test
+ public void update_existing_global_property() {
+ propertyDb.insertProperty(newGlobalProperty("my.key", "my value"));
+ assertGlobalProperty("my.key", "my value");
+
+ callForGlobalProperty("my.key", "my new value");
+
+ assertGlobalProperty("my.key", "my new value");
+ }
+
+ @Test
+ public void persist_new_project_property() {
+ propertyDb.insertProperty(newGlobalProperty("my.key", "my global value"));
+ ComponentDto project = componentDb.insertProject();
+
+ callForProjectPropertyByUuid("my.key", "my project value", project.uuid());
+
+ assertGlobalProperty("my.key", "my global value");
+ assertProjectProperty("my.key", "my project value", project.getId());
+ }
+
+ @Test
+ public void persist_project_property_with_project_admin_permission() {
+ ComponentDto project = componentDb.insertProject();
+ userSession.anonymous().addProjectUuidPermissions(UserRole.ADMIN, project.uuid());
+
+ callForProjectPropertyByUuid("my.key", "my value", project.uuid());
+
+ assertProjectProperty("my.key", "my value", project.getId());
+ }
+
+ @Test
+ public void update_existing_project_property() {
+ propertyDb.insertProperty(newGlobalProperty("my.key", "my global value"));
+ ComponentDto project = componentDb.insertProject();
+ propertyDb.insertProperty(newProjectProperty("my.key", "my project value", project.getId()));
+ assertProjectProperty("my.key", "my project value", project.getId());
+
+ callForProjectPropertyByKey("my.key", "my new project value", project.key());
+
+ assertProjectProperty("my.key", "my new project value", project.getId());
+ }
+
+ @Test
+ public void user_property_is_not_updated() {
+ propertyDb.insertProperty(newGlobalProperty("my.key", "my user value").setUserId(42L));
+ propertyDb.insertProperty(newGlobalProperty("my.key", "my global value"));
+
+ callForGlobalProperty("my.key", "my new global value");
+
+ assertGlobalProperty("my.key", "my new global value");
+ assertUserProperty("my.key", "my user value", 42L);
+ }
+
+ @Test
+ public void fail_when_no_key() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ callForGlobalProperty(null, "my value");
+ }
+
+ @Test
+ public void fail_when_empty_key_value() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Setting key is mandatory and must not be empty.");
+
+ callForGlobalProperty(" ", "my value");
+ }
+
+ @Test
+ public void fail_when_no_value() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ callForGlobalProperty("my.key", null);
+ }
+
+ @Test
+ public void fail_when_empty_value() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Setting value is mandatory and must not be empty.");
+
+ callForGlobalProperty("my.key", "");
+ }
+
+ @Test
+ public void fail_when_insufficient_privileges() {
+ userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
+ expectedException.expect(ForbiddenException.class);
+
+ callForGlobalProperty("my.key", "my value");
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("set");
+ assertThat(definition.isPost()).isTrue();
+ assertThat(definition.since()).isEqualTo("6.1");
+ assertThat(definition.params()).extracting(Param::key)
+ .containsOnlyOnce("key", "value");
+ }
+
+ private void assertGlobalProperty(String key, String value) {
+ PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key);
+
+ assertThat(result)
+ .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId)
+ .containsExactly(key, value, null);
+ }
+
+ private void assertUserProperty(String key, String value, long userId) {
+ List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setKey(key).setUserId((int) userId).build(), dbSession);
+
+ assertThat(result).hasSize(1)
+ .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getUserId)
+ .containsExactly(tuple(key, value, userId));
+ }
+
+ private void assertProjectProperty(String key, String value, long componentId) {
+ PropertyDto result = dbClient.propertiesDao().selectProjectProperty(componentId, key);
+
+ assertThat(result)
+ .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId)
+ .containsExactly(key, value, componentId);
+ }
+
+ private void callForGlobalProperty(@Nullable String key, @Nullable String value) {
+ call(key, value, null, null);
+ }
+
+ private void callForProjectPropertyByUuid(@Nullable String key, @Nullable String value, @Nullable String componentUuid) {
+ call(key, value, componentUuid, null);
+ }
+
+ private void callForProjectPropertyByKey(@Nullable String key, @Nullable String value, @Nullable String componentKey) {
+ call(key, value, null, componentKey);
+ }
+
+ private void call(@Nullable String key, @Nullable String value, @Nullable String componentUuid, @Nullable String componentKey) {
+ TestRequest request = ws.newRequest();
+
+ if (key != null) {
+ request.setParam("key", key);
+ }
+
+ if (value != null) {
+ request.setParam("value", value);
+ }
+
+ if (componentUuid != null) {
+ request.setParam("componentId", componentUuid);
+ }
+
+ if (componentKey != null) {
+ request.setParam("componentKey", componentKey);
+ }
+
+ request.execute();
+ }
+}
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new SettingsWsModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 2);
+ assertThat(container.size()).isEqualTo(3 + 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.db.property;
+
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+public class PropertyDbTester {
+ private final DbTester db;
+ private final DbClient dbClient;
+ private final DbSession dbSession;
+
+ public PropertyDbTester(DbTester db) {
+ this.db = db;
+ this.dbClient = db.getDbClient();
+ this.dbSession = db.getSession();
+ }
+
+ public PropertyDto insertProperty(PropertyDto property) {
+ dbClient.propertiesDao().insertProperty(dbSession, property);
+ db.commit();
+
+ return property;
+ }
+}
--- /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.db.property;
+
+public class PropertyTesting {
+
+ public static PropertyDto newGlobalProperty(String key, String value) {
+ return new PropertyDto().setKey(key).setValue(value);
+ }
+
+ public static PropertyDto newProjectProperty(String key, String value, long componentId) {
+ return new PropertyDto().setKey(key).setValue(value).setResourceId(componentId);
+ }
+}
--- /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.sonarqube.ws.client.setting;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class SetRequest {
+ private final String key;
+ private final String value;
+ private final String componentId;
+ private final String componentKey;
+
+ public SetRequest(Builder builder) {
+ this.key = builder.key;
+ this.value = builder.value;
+ this.componentId = builder.componentId;
+ this.componentKey = builder.componentKey;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @CheckForNull
+ public String getComponentId() {
+ return componentId;
+ }
+
+ @CheckForNull
+ public String getComponentKey() {
+ return componentKey;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String key;
+ private String value;
+ private String componentId;
+ private String componentKey;
+
+ private Builder() {
+ // enforce factory method use
+ }
+
+ public Builder setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setValue(String value) {
+ this.value = value;
+ return this;
+ }
+
+ public Builder setComponentId(@Nullable String componentId) {
+ this.componentId = componentId;
+ return this;
+ }
+
+ public Builder setComponentKey(@Nullable String componentKey) {
+ this.componentKey = componentKey;
+ return this;
+ }
+
+ public SetRequest build() {
+ checkArgument(key != null && !key.isEmpty(), "Setting key is mandatory and must not be empty.");
+ checkArgument(value != null && !value.isEmpty(), "Setting value is mandatory and must not be empty.");
+ return new SetRequest(this);
+ }
+ }
+}
--- /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.sonarqube.ws.client.setting;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SetRequestTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ SetRequest.Builder underTest = SetRequest.builder();
+
+ @Test
+ public void create_set_request() {
+ SetRequest result = underTest.setKey("my.key").setValue("my value").build();
+
+ assertThat(result.getKey()).isEqualTo("my.key");
+ assertThat(result.getValue()).isEqualTo("my value");
+ }
+
+ @Test
+ public void fail_when_empty_key() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ underTest
+ .setKey("")
+ .setValue("value")
+ .build();
+ }
+
+ @Test
+ public void fail_when_empty_value() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ underTest
+ .setKey("key")
+ .setValue(null)
+ .build();
+ }
+}