Browse Source

SONAR-6472 New WS to create user groups

tags/5.2-RC1
Jean-Baptiste Lievremont 9 years ago
parent
commit
f63ea546f6

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -495,6 +495,7 @@ public class PlatformLevel4 extends PlatformLevel {
GroupMembershipFinder.class,
UserGroupsWs.class,
org.sonar.server.usergroups.ws.SearchAction.class,
org.sonar.server.usergroups.ws.CreateAction.class,

// permissions
PermissionFacade.class,

+ 134
- 0
server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/CreateAction.java View File

@@ -0,0 +1,134 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.usergroups.ws;

import com.google.common.base.Preconditions;
import java.net.HttpURLConnection;
import javax.annotation.Nullable;
import org.sonar.api.security.DefaultGroups;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService.NewAction;
import org.sonar.api.server.ws.WebService.NewController;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.user.GroupDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.exceptions.ServerException;
import org.sonar.server.user.UserSession;

import static org.sonar.core.persistence.MyBatis.closeQuietly;

public class CreateAction implements UserGroupsWsAction {

private static final String PARAM_DESCRIPTION = "description";
private static final String PARAM_NAME = "name";

// Database column size should be 500 (since migration #353),
// but on some instances, column size is still 255,
// hence the validation is done with 255
private static final int NAME_MAX_LENGTH = 255;
private static final int DESCRIPTION_MAX_LENGTH = 200;

private final DbClient dbClient;
private final UserSession userSession;

public CreateAction(DbClient dbClient, UserSession userSession) {
this.dbClient = dbClient;
this.userSession = userSession;
}

@Override
public void define(NewController context) {
NewAction action = context.createAction("create")
.setDescription("Create a group.")
.setHandler(this)
.setPost(true)
.setResponseExample(getClass().getResource("example-create.json"))
.setSince("5.2");

action.createParam(PARAM_NAME)
.setDescription("Name for the new group. A group name cannot be larger than 255 characters and must be unique. " +
"The value 'anyone' (whatever the case) is reserved and cannot be used.")
.setExampleValue("sonar-users")
.setRequired(true);

action.createParam(PARAM_DESCRIPTION)
.setDescription("Description for the new group. A group description cannot be larger than 200 characters.")
.setExampleValue("Default group for new users");
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);

String name = request.mandatoryParam(PARAM_NAME);
String description = request.param(PARAM_DESCRIPTION);

validateName(name);
validateDescription(description);

GroupDto newGroup = new GroupDto().setName(name).setDescription(description);
DbSession session = dbClient.openSession(false);
try {
checkNameIsUnique(name, session);
newGroup = dbClient.groupDao().insert(session, new GroupDto().setName(name).setDescription(description));
session.commit();
} finally {
closeQuietly(session);
}

response.newJsonWriter().beginObject().name("group").beginObject()
.prop("id", newGroup.getId().toString())
.prop(PARAM_NAME, newGroup.getName())
.prop(PARAM_DESCRIPTION, newGroup.getDescription())
.prop("membersCount", 0)
.endObject().endObject().close();
}

private void validateName(String name) {
checkNameLength(name);
checkNameNotAnyone(name);
}

private void checkNameLength(String name) {
Preconditions.checkArgument(!name.isEmpty(), "Name cannot be empty");
Preconditions.checkArgument(name.length() <= NAME_MAX_LENGTH, String.format("Name cannot be longer than %d characters", NAME_MAX_LENGTH));
}

private void checkNameNotAnyone(String name) {
Preconditions.checkArgument(!DefaultGroups.isAnyone(name), String.format("Name '%s' is reserved (regardless of case)", DefaultGroups.ANYONE));
}

private void checkNameIsUnique(String name, DbSession session) {
// There is no database constraint on column groups.name
// because MySQL cannot create a unique index
// on a UTF-8 VARCHAR larger than 255 characters on InnoDB
if (dbClient.groupDao().selectByKey(session, name) != null) {
throw new ServerException(HttpURLConnection.HTTP_CONFLICT, String.format("Name '%s' is already taken", name));
}
}

private void validateDescription(@Nullable String description) {
if (description != null) {
Preconditions.checkArgument(description.length() <= DESCRIPTION_MAX_LENGTH, String.format("Description cannot be longer than %d characters", DESCRIPTION_MAX_LENGTH));
}
}
}

+ 8
- 0
server/sonar-server/src/main/resources/org/sonar/server/usergroups/ws/example-create.json View File

@@ -0,0 +1,8 @@
{
"group": {
"id": "42",
"name": "some-product-bu",
"description": "Business Unit for Some Awesome Product",
"membersCount": 0
}
}

+ 157
- 0
server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/CreateActionTest.java View File

@@ -0,0 +1,157 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.usergroups.ws;

import java.net.HttpURLConnection;
import org.apache.commons.lang.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.DbTester;
import org.sonar.core.user.GroupDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.ServerException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.db.GroupDao;
import org.sonar.server.ws.WsTester;
import org.sonar.test.DbTests;

@Category(DbTests.class)
public class CreateActionTest {

@ClassRule
public static final DbTester dbTester = new DbTester();

@Rule
public UserSessionRule userSession = UserSessionRule.standalone();

@Rule
public ExpectedException expectedException = ExpectedException.none();

private WsTester tester;

private GroupDao groupDao;

private DbSession session;

@Before
public void setUp() {
dbTester.truncateTables();

groupDao = new GroupDao(System2.INSTANCE);

DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), groupDao);

tester = new WsTester(new UserGroupsWs(new CreateAction(dbClient, userSession)));

session = dbClient.openSession(false);
}

@After
public void after() {
session.close();
}

@Test
public void create_nominal() throws Exception {
loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", "some-product-bu")
.setParam("description", "Business Unit for Some Awesome Product")
.execute().assertJson("{" +
" \"group\": {" +
" \"name\": \"some-product-bu\"," +
" \"description\": \"Business Unit for Some Awesome Product\"," +
" \"membersCount\": 0" +
" }" +
"}");
}

@Test(expected = ForbiddenException.class)
public void require_admin_permission() throws Exception {
userSession.login("not-admin");
tester.newPostRequest("api/usergroups", "create")
.setParam("name", "some-product-bu")
.setParam("description", "Business Unit for Some Awesome Product")
.execute();
}

@Test(expected = IllegalArgumentException.class)
public void name_too_short() throws Exception {
loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", "")
.execute();
}

@Test(expected = IllegalArgumentException.class)
public void name_too_long() throws Exception {
loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", StringUtils.repeat("a", 255 + 1))
.execute();
}

@Test(expected = IllegalArgumentException.class)
public void forbidden_name() throws Exception {
loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", "AnYoNe")
.execute();
}

@Test
public void non_unique_name() throws Exception {
String groupName = "conflicting-name";
groupDao.insert(session, new GroupDto()
.setName(groupName));
session.commit();

expectedException.expect(ServerException.class);
expectedException.expectMessage("already taken");

loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", groupName)
.execute().assertStatus(HttpURLConnection.HTTP_CONFLICT);
}

@Test(expected = IllegalArgumentException.class)
public void description_too_long() throws Exception {
loginAsAdmin();
tester.newPostRequest("api/usergroups", "create")
.setParam("name", "long-group-description-is-looooooooooooong")
.setParam("description", StringUtils.repeat("a", 200 + 1))
.execute();
}

private void loginAsAdmin() {
userSession.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
}
}

+ 14
- 3
server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UserGroupsWsTest.java View File

@@ -26,6 +26,7 @@ import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.db.DbClient;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.WsTester;

import static org.assertj.core.api.Assertions.assertThat;
@@ -38,7 +39,9 @@ public class UserGroupsWsTest {

@Before
public void setUp() {
WsTester tester = new WsTester(new UserGroupsWs(new SearchAction(mock(DbClient.class))));
WsTester tester = new WsTester(new UserGroupsWs(
new SearchAction(mock(DbClient.class)),
new CreateAction(mock(DbClient.class), mock(UserSession.class))));
controller = tester.controller("api/usergroups");
}

@@ -47,15 +50,23 @@ public class UserGroupsWsTest {
assertThat(controller).isNotNull();
assertThat(controller.description()).isNotEmpty();
assertThat(controller.since()).isEqualTo("5.2");
assertThat(controller.actions()).hasSize(1);
assertThat(controller.actions()).hasSize(2);
}

@Test
public void define_search_action() {
WebService.Action action = controller.action("search");
assertThat(action).isNotNull();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
assertThat(action.params()).hasSize(4);
}

@Test
public void define_create_action() {
WebService.Action action = controller.action("create");
assertThat(action).isNotNull();
assertThat(action.isPost()).isTrue();
assertThat(action.responseExampleAsString()).isNotEmpty();
assertThat(action.params()).hasSize(2);
}
}

+ 2
- 4
sonar-core/src/main/java/org/sonar/core/user/GroupMapper.java View File

@@ -20,11 +20,9 @@

package org.sonar.core.user;

import org.apache.ibatis.session.RowBounds;

import javax.annotation.CheckForNull;

import java.util.List;
import javax.annotation.CheckForNull;
import org.apache.ibatis.session.RowBounds;

public interface GroupMapper {


+ 1
- 1
sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl View File

@@ -82,7 +82,7 @@ CREATE TABLE "WIDGETS" (

CREATE TABLE "GROUPS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"NAME" VARCHAR(255),
"NAME" VARCHAR(500),
"DESCRIPTION" VARCHAR(200),
"CREATED_AT" TIMESTAMP,
"UPDATED_AT" TIMESTAMP

Loading…
Cancel
Save