diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2017-03-09 16:18:31 +0100 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-03-21 13:05:50 +0100 |
commit | 25a9f85391d0391d84402f620e25c19f25769474 (patch) | |
tree | 1cd84e9d086faef6440dfbe37b550b8a379efac5 /server/sonar-server | |
parent | 1ac32ede34b0ff29bf0af042e4ce359b5999fcd4 (diff) | |
download | sonarqube-25a9f85391d0391d84402f620e25c19f25769474.tar.gz sonarqube-25a9f85391d0391d84402f620e25c19f25769474.zip |
SONAR-8892 Create WS api/organizations/add_member
WS adds a member to an organization
Diffstat (limited to 'server/sonar-server')
6 files changed, 298 insertions, 4 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java new file mode 100644 index 00000000000..b226a205cd9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/AddMemberAction.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.organization.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.server.ws.WebService.NewController; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.organization.OrganizationMemberDto; +import org.sonar.db.permission.OrganizationPermission; +import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.user.UserSession; + +import static java.lang.String.format; +import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_LOGIN; +import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION; +import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001; +import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; + +public class AddMemberAction implements OrganizationsAction { + private final DbClient dbClient; + private final UuidFactory uuidFactory; + private final UserSession userSession; + + public AddMemberAction(DbClient dbClient, UuidFactory uuidFactory, UserSession userSession) { + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + this.userSession = userSession; + } + + @Override + public void define(NewController context) { + WebService.NewAction action = context.createAction("add_member") + .setDescription("Add a user as a member of an organization.<br>" + + "Requires 'Administer System' permission on the specified organization.") + .setSince("6.4") + .setPost(true) + .setInternal(true) + .setHandler(this); + + action + .createParam(PARAM_ORGANIZATION) + .setDescription("Organization key") + .setRequired(true) + .setExampleValue(KEY_ORG_EXAMPLE_001); + + action + .createParam("login") + .setDescription("User login") + .setRequired(true) + .setExampleValue("ray.bradbury"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String organizationKey = request.mandatoryParam(PARAM_ORGANIZATION); + String login = request.mandatoryParam(PARAM_LOGIN); + + try (DbSession dbSession = dbClient.openSession(false)) { + OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "Organization '%s' is not found", + organizationKey); + UserDto user = checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' is not found", login); + + userSession.checkPermission(OrganizationPermission.ADMINISTER, organization); + + OrganizationMemberDto organizationMember = new OrganizationMemberDto() + .setOrganizationUuid(organization.getUuid()) + .setUserId(user.getId()); + + dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId()) + .ifPresent(o -> { + throw BadRequestException.create(format("User '%s' is already a member of organization '%s'", user.getLogin(), organization.getKey())); + }); + + dbClient.organizationMemberDao().insert(dbSession, organizationMember); + dbSession.commit(); + } + + response.noContent(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java index c05b428bc39..31ea3feb687 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java @@ -29,12 +29,13 @@ public class OrganizationsWsModule extends Module { OrganizationsWs.class, OrganizationsWsSupport.class, // actions + AddMemberAction.class, CreateAction.class, + DeleteAction.class, EnableSupportAction.class, SearchAction.class, - UpdateAction.class, - DeleteAction.class, - SearchMyOrganizationsAction.class); + SearchMyOrganizationsAction.class, + UpdateAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java index 42019093cd6..41131784aa4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java @@ -32,12 +32,15 @@ import static org.sonar.core.util.Protobuf.setNullable; * Factorizes code and constants between Organization WS's actions. */ public class OrganizationsWsSupport { + static final String PARAM_ORGANIZATION = "organization"; static final String PARAM_KEY = "key"; static final String PARAM_NAME = "name"; static final String PARAM_DESCRIPTION = "description"; static final String PARAM_URL = "url"; static final String PARAM_AVATAR_URL = "avatar"; + static final String PARAM_LOGIN = "login"; + private final OrganizationValidation organizationValidation; public OrganizationsWsSupport(OrganizationValidation organizationValidation) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java index 450d42348fb..ee140c23c3d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java @@ -27,6 +27,8 @@ public class KeyExamples { public static final String KEY_PROJECT_EXAMPLE_003 = "third_project"; public static final String KEY_DEVELOPER_EXAMPLE_001 = "DEV:ada@lovelace.com"; + public static final String KEY_ORG_EXAMPLE_001 = "my-org"; + private KeyExamples() { // prevent instantiation } diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/AddMemberActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/AddMemberActionTest.java new file mode 100644 index 00000000000..66d094ad76d --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/AddMemberActionTest.java @@ -0,0 +1,183 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.organization.ws; + +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +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 java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_GATES; +import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION; + +public class AddMemberActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot(); + @Rule + public DbTester db = DbTester.create(); + private DbClient dbClient = db.getDbClient(); + private DbSession dbSession = db.getSession(); + + private WsActionTester ws = new WsActionTester(new AddMemberAction(dbClient, new SequenceUuidFactory(), userSession)); + + private OrganizationDto organization; + private UserDto user; + + @Before + public void setUp() { + organization = db.organizations().insert(); + user = db.users().insertUser(); + } + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.key()).isEqualTo("add_member"); + assertThat(definition.since()).isEqualTo("6.4"); + assertThat(definition.isPost()).isTrue(); + assertThat(definition.isInternal()).isTrue(); + assertThat(definition.params()).extracting(WebService.Param::key).containsOnly("organization", "login"); + + WebService.Param organization = definition.param("organization"); + assertThat(organization.isRequired()).isTrue(); + + WebService.Param login = definition.param("login"); + assertThat(login.isRequired()).isTrue(); + } + + @Test + public void no_content_http_204_returned() { + TestResponse result = call(organization.getKey(), user.getLogin()); + + assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT); + assertThat(result.getInput()).isEmpty(); + } + + @Test + public void add_member_in_db() { + call(organization.getKey(), user.getLogin()); + + assertMember(organization.getUuid(), user.getId()); + } + + @Test + public void user_can_be_member_of_two_organizations() { + OrganizationDto anotherOrg = db.organizations().insert(); + + call(organization.getKey(), user.getLogin()); + call(anotherOrg.getKey(), user.getLogin()); + + assertMember(organization.getUuid(), user.getId()); + assertMember(anotherOrg.getUuid(), user.getId()); + } + + @Test + public void add_member_as_organization_admin() { + userSession.logIn().addPermission(ADMINISTER, organization); + + call(organization.getKey(), user.getLogin()); + + assertMember(organization.getUuid(), user.getId()); + } + + @Test + public void fail_if_login_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("User 'login-42' is not found"); + + call(organization.getKey(), "login-42"); + } + + @Test + public void fail_if_organization_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Organization 'org-42' is not found"); + + call("org-42", user.getLogin()); + } + + @Test + public void fail_if_no_login_provided() { + expectedException.expect(IllegalArgumentException.class); + + call(organization.getKey(), null); + } + + @Test + public void fail_if_no_organization_provided() { + expectedException.expect(IllegalArgumentException.class); + + call(null, user.getLogin()); + } + + @Test + public void fail_if_user_already_added_in_organization() { + call(organization.getKey(), user.getLogin()); + + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("User '" + user.getLogin() + "' is already a member of organization '" + organization.getKey() + "'"); + + call(organization.getKey(), user.getLogin()); + } + + @Test + public void fail_if_insufficient_permissions() { + userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES, organization); + + expectedException.expect(ForbiddenException.class); + + call(organization.getKey(), user.getLogin()); + } + + private TestResponse call(@Nullable String organizationKey, @Nullable String login) { + TestRequest request = ws.newRequest(); + setNullable(organizationKey, o -> request.setParam(PARAM_ORGANIZATION, o)); + setNullable(login, l -> request.setParam("login", l)); + + return request.execute(); + } + + private void assertMember(String organizationUuid, int userId) { + assertThat(dbClient.organizationMemberDao().select(dbSession, organizationUuid, userId)).isPresent(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java index eccaae0ecc5..7c833fd7190 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java @@ -33,7 +33,7 @@ public class OrganizationsWsModuleTest { ComponentContainer container = new ComponentContainer(); underTest.configure(container); assertThat(container.getPicoContainer().getComponentAdapters()) - .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8); + .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 9); } } |