]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6479 WS permissions/users search users with a specific permission 459/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 4 Aug 2015 13:29:06 +0000 (15:29 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 6 Aug 2015 07:05:46 +0000 (09:05 +0200)
24 files changed:
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionFinder.java
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionQueryParser.java
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java
server/sonar-server/src/main/java/org/sonar/server/permission/UserWithPermissionQueryResult.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java
server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/PermissionFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java
server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/permission/PermissionDao.java
sonar-db/src/main/java/org/sonar/db/permission/PermissionMapper.java
sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java
sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java
sonar-db/src/main/resources/org/sonar/db/permission/PermissionMapper.xml
sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml
sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java
sonar-db/src/test/java/org/sonar/db/permission/UserWithPermissionDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/UserTesting.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-permissions.proto [new file with mode: 0644]

index 026c6adff6504c5e76351a33dab838464bb3e971..c9aa9271405e3f3c8a8120ab74f63c435d5470f8 100644 (file)
@@ -30,16 +30,18 @@ import org.sonar.api.security.DefaultGroups;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.Paging;
 import org.sonar.core.permission.GroupWithPermission;
+import org.sonar.core.permission.UserWithPermission;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+import org.sonar.db.component.ResourceQuery;
 import org.sonar.db.permission.GroupWithPermissionDto;
 import org.sonar.db.permission.PermissionDao;
 import org.sonar.db.permission.PermissionQuery;
 import org.sonar.db.permission.PermissionTemplateDao;
 import org.sonar.db.permission.PermissionTemplateDto;
-import org.sonar.core.permission.UserWithPermission;
 import org.sonar.db.permission.UserWithPermissionDto;
-import org.sonar.db.component.ResourceDao;
-import org.sonar.db.component.ResourceDto;
-import org.sonar.db.component.ResourceQuery;
 import org.sonar.server.exceptions.NotFoundException;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -47,27 +49,41 @@ import static com.google.common.collect.Lists.newArrayList;
 @ServerSide
 public class PermissionFinder {
 
+  private final DbClient dbClient;
+
   private final PermissionDao permissionDao;
   private final ResourceDao resourceDao;
-
   private final PermissionTemplateDao permissionTemplateDao;
 
-  public PermissionFinder(PermissionDao permissionDao, ResourceDao resourceDao, PermissionTemplateDao permissionTemplateDao) {
-    this.resourceDao = resourceDao;
-    this.permissionDao = permissionDao;
-    this.permissionTemplateDao = permissionTemplateDao;
+  public PermissionFinder(DbClient dbClient) {
+    this.dbClient = dbClient;
+    this.resourceDao = dbClient.resourceDao();
+    this.permissionDao = dbClient.permissionDao();
+    this.permissionTemplateDao = dbClient.permissionTemplateDao();
   }
 
   public UserWithPermissionQueryResult findUsersWithPermission(PermissionQuery query) {
     Long componentId = componentId(query.component());
-    int limit = limit(query);
-    return toUserQueryResult(permissionDao.selectUsers(query, componentId, offset(query), limit), limit);
+    int limit = query.pageSize();
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      int total = permissionDao.countUsers(dbSession, query, componentId);
+      return toUserQueryResult(permissionDao.selectUsers(dbSession, query, componentId, offset(query), limit), total);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
   }
 
   public UserWithPermissionQueryResult findUsersWithPermissionTemplate(PermissionQuery query) {
     Long permissionTemplateId = templateId(query.template());
-    int limit = limit(query);
-    return toUserQueryResult(permissionTemplateDao.selectUsers(query, permissionTemplateId, offset(query), limit), limit);
+    int limit = query.pageSize();
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      int total = permissionTemplateDao.countUsers(dbSession, query, permissionTemplateId);
+      return toUserQueryResult(permissionTemplateDao.selectUsers(dbSession, query, permissionTemplateId, offset(query), limit), total);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
   }
 
   /**
@@ -86,14 +102,8 @@ public class PermissionFinder {
     return toGroupQueryResult(permissionTemplateDao.selectGroups(query, permissionTemplateId), query);
   }
 
-  private static UserWithPermissionQueryResult toUserQueryResult(List<UserWithPermissionDto> dtos, int limit) {
-    boolean hasMoreResults = false;
-    if (dtos.size() == limit) {
-      hasMoreResults = true;
-      // Removed last entry as it's only need to know if there more results or not
-      dtos.remove(dtos.size() - 1);
-    }
-    return new UserWithPermissionQueryResult(toUserWithPermissionList(dtos), hasMoreResults);
+  private static UserWithPermissionQueryResult toUserQueryResult(List<UserWithPermissionDto> dtos, int total) {
+    return new UserWithPermissionQueryResult(toUserWithPermissionList(dtos), total);
   }
 
   private static List<UserWithPermission> toUserWithPermissionList(List<UserWithPermissionDto> dtos) {
@@ -140,11 +150,6 @@ public class PermissionFinder {
     return (pageIndex - 1) * pageSize;
   }
 
-  private static int limit(PermissionQuery query) {
-    // Add one to page size in order to be able to know if there's more results or not
-    return query.pageSize() + 1;
-  }
-
   private List<GroupWithPermissionDto> filterMembership(List<GroupWithPermissionDto> dtos, PermissionQuery query) {
     return newArrayList(Iterables.filter(dtos, new GroupWithPermissionMatchQuery(query)));
   }
index e8a5c67fa88d8fe5d5da02a9f2629875d894ccc3..0b0fead64f8f90192964ebee9faa425d4fa359d6 100644 (file)
 
 package org.sonar.server.permission;
 
+import java.util.Map;
 import org.sonar.api.server.ws.WebService.SelectionMode;
-
 import org.sonar.db.permission.PermissionQuery;
 import org.sonar.db.user.GroupMembershipQuery;
 import org.sonar.server.util.RubyUtils;
 
-import java.util.Map;
-
 public class PermissionQueryParser {
 
-  private PermissionQueryParser(){
+  private PermissionQueryParser() {
     // Utility class
   }
 
@@ -39,15 +37,15 @@ public class PermissionQueryParser {
     builder.permission((String) params.get("permission"));
     builder.component((String) params.get("component"));
     builder.template((String) params.get("template"));
-    builder.membership(membership(params));
+    builder.membership(toMembership((String) params.get("selected")));
     builder.search((String) params.get("query"));
     builder.pageIndex(RubyUtils.toInteger(params.get("page")));
     builder.pageSize(RubyUtils.toInteger(params.get("pageSize")));
     return builder.build();
   }
 
-  private static String membership(Map<String, Object> params) {
-    SelectionMode selectionMode = SelectionMode.fromParam((String) params.get("selected"));
+  public static String toMembership(String selectionModeString) {
+    SelectionMode selectionMode = SelectionMode.fromParam(selectionModeString);
     if (SelectionMode.SELECTED == selectionMode) {
       return GroupMembershipQuery.IN;
     } else if (SelectionMode.DESELECTED == selectionMode) {
@@ -57,5 +55,4 @@ public class PermissionQueryParser {
     }
   }
 
-
 }
index feef6db4848f099e288379a81d08785338434c6f..28ad37a29553efe952df1618a381c2af6ec32ab2 100644 (file)
@@ -47,10 +47,11 @@ import org.sonar.server.user.UserSession;
 @ServerSide
 public class PermissionService {
 
+
+
   private enum Operation {
-    ADD, REMOVE
+    ADD, REMOVE;
   }
-
   private static final String OBJECT_TYPE_USER = "User";
   private static final String OBJECT_TYPE_GROUP = "Group";
   private static final String NOT_FOUND_FORMAT = "%s %s does not exist";
@@ -61,7 +62,6 @@ public class PermissionService {
   private final IssueAuthorizationIndexer issueAuthorizationIndexer;
   private final UserSession userSession;
   private final ComponentFinder componentFinder;
-
   public PermissionService(DbClient dbClient, PermissionRepository permissionRepository, PermissionFinder finder,
     IssueAuthorizationIndexer issueAuthorizationIndexer, UserSession userSession, ComponentFinder componentFinder) {
     this.dbClient = dbClient;
index 621ce03ef5c0d655c19ccc3bb2322ce1e9cd99ed..aec170fb6e59453fbc69b4dbaf87a5d86a7c0597 100644 (file)
 
 package org.sonar.server.permission;
 
-import org.sonar.core.permission.UserWithPermission;
-
 import java.util.List;
+import org.sonar.core.permission.UserWithPermission;
 
 public class UserWithPermissionQueryResult {
 
-  private List<UserWithPermission> users;
-  private boolean hasMoreResults;
+  private final List<UserWithPermission> users;
+  private final int total;
+  private final boolean hasMoreResults;
 
-  public UserWithPermissionQueryResult(List<UserWithPermission> users, boolean hasMoreResults) {
+  public UserWithPermissionQueryResult(List<UserWithPermission> users, int total) {
     this.users = users;
-    this.hasMoreResults = hasMoreResults;
+    this.total = total;
+    this.hasMoreResults = total > users.size();
   }
 
   public List<UserWithPermission> users() {
     return users;
   }
 
+  public int total() {
+    return total;
+  }
+
+  // called by Ruby Code
   public boolean hasMoreResults() {
     return hasMoreResults;
   }
-
 }
index 246f172056ba895146b721a54f3d6ef574f02323..b69487adaa87392e767a2834d841ca7f2932f1ef 100644 (file)
@@ -30,6 +30,7 @@ public class PermissionsWsModule extends Module {
       AddGroupAction.class,
       AddUserAction.class,
       RemoveGroupAction.class,
-      RemoveUserAction.class);
+      RemoveUserAction.class,
+      UsersAction.class);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java
new file mode 100644 (file)
index 0000000..fc3cafe
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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.permission.ws;
+
+import com.google.common.io.Resources;
+import java.util.List;
+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.Param;
+import org.sonar.api.server.ws.WebService.SelectionMode;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.permission.UserWithPermission;
+import org.sonar.core.util.ProtobufJsonFormat;
+import org.sonar.db.permission.PermissionQuery;
+import org.sonar.server.permission.PermissionFinder;
+import org.sonar.server.permission.UserWithPermissionQueryResult;
+import org.sonar.server.plugins.MimeTypes;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Permissions;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static org.sonar.server.permission.PermissionQueryParser.toMembership;
+
+public class UsersAction implements PermissionsWsAction {
+
+  private final UserSession userSession;
+  private final PermissionFinder permissionFinder;
+
+  public UsersAction(UserSession userSession, PermissionFinder permissionFinder) {
+    this.userSession = userSession;
+    this.permissionFinder = permissionFinder;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction("users")
+      .setSince("5.2")
+      .setDescription(String.format("List permission's users.<br /> " +
+        "If the query parameter '%s' is specified, the '%s' parameter is '%s'.",
+        Param.TEXT_QUERY, Param.SELECTED, SelectionMode.ALL.value()))
+      .addPagingParams(100)
+      .addSearchQuery("stas", "names")
+      .addSelectionModeParam()
+      .setInternal(true)
+      .setResponseExample(Resources.getResource(getClass(), "users-example.json"))
+      .setHandler(this);
+
+    action.createParam("permission")
+      .setExampleValue("scan")
+      .setRequired(true)
+      .setPossibleValues(GlobalPermissions.ALL);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String permission = request.mandatoryParam("permission");
+    String selected = request.param(Param.SELECTED);
+    int page = request.mandatoryParamAsInt(Param.PAGE);
+    int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+    String query = request.param(Param.TEXT_QUERY);
+    if (query != null) {
+      selected = SelectionMode.ALL.value();
+    }
+
+    userSession
+      .checkLoggedIn()
+      .checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+
+    PermissionQuery.Builder permissionQuery = PermissionQuery.builder()
+      .permission(permission)
+      .pageIndex(page)
+      .pageSize(pageSize)
+      .membership(toMembership(firstNonNull(selected, SelectionMode.SELECTED.value())));
+    if (query != null) {
+      permissionQuery.search(query);
+    }
+
+    UserWithPermissionQueryResult usersResult = permissionFinder.findUsersWithPermission(permissionQuery.build());
+    List<UserWithPermission> usersWithPermission = usersResult.users();
+
+    Permissions.UsersResponse.Builder userResponse = Permissions.UsersResponse.newBuilder();
+    Permissions.UsersResponse.User.Builder user = Permissions.UsersResponse.User.newBuilder();
+    Common.Paging.Builder paging = Common.Paging.newBuilder();
+    for (UserWithPermission userWithPermission : usersWithPermission) {
+      userResponse.addUsers(
+        user
+          .clear()
+          .setLogin(userWithPermission.login())
+          .setName(userWithPermission.name())
+          .setSelected(userWithPermission.hasPermission()));
+      userResponse.setPaging(
+        paging
+          .clear()
+          .setPages(page)
+          .setPageSize(pageSize)
+          .setTotal(usersResult.total())
+        );
+    }
+
+    response.stream().setMediaType(MimeTypes.JSON);
+    JsonWriter json = response.newJsonWriter();
+    ProtobufJsonFormat.write(userResponse.build(), json);
+    json.close();
+  }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json
new file mode 100644 (file)
index 0000000..b4bd9da
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "users": [
+    {
+      "login": "admin",
+      "name": "Administrator",
+      "selected": true
+    },
+    {
+      "login": "george.orwell",
+      "name": "George Orwell",
+      "selected": true
+    }
+  ],
+  "paging": {
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  }
+}
+
index 293092c91276ae6c95114cb041a09878de2a7fee..440878a0c8bec99e3b3fa7634cea98c765306d61 100644 (file)
@@ -27,15 +27,17 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.core.permission.GroupWithPermission;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ResourceDao;
+import org.sonar.db.component.ResourceDto;
+import org.sonar.db.component.ResourceQuery;
 import org.sonar.db.permission.GroupWithPermissionDto;
 import org.sonar.db.permission.PermissionDao;
 import org.sonar.db.permission.PermissionQuery;
 import org.sonar.db.permission.PermissionTemplateDao;
 import org.sonar.db.permission.PermissionTemplateDto;
 import org.sonar.db.permission.UserWithPermissionDto;
-import org.sonar.db.component.ResourceDao;
-import org.sonar.db.component.ResourceDto;
-import org.sonar.db.component.ResourceQuery;
 import org.sonar.server.exceptions.NotFoundException;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -45,6 +47,7 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -60,23 +63,28 @@ public class PermissionFinderTest {
   @Mock
   PermissionTemplateDao permissionTemplateDao;
 
-  PermissionFinder finder;
+  PermissionFinder underTest;
 
   @Before
   public void setUp() {
+    DbClient dbClient = mock(DbClient.class);
+    when(dbClient.resourceDao()).thenReturn(resourceDao);
+    when(dbClient.permissionDao()).thenReturn(permissionDao);
+    when(dbClient.permissionTemplateDao()).thenReturn(permissionTemplateDao);
     when(resourceDao.selectResource(any(ResourceQuery.class))).thenReturn(new ResourceDto().setId(100L).setName("org.sample.Sample"));
-    finder = new PermissionFinder(permissionDao, resourceDao, permissionTemplateDao);
+    underTest = new PermissionFinder(dbClient);
   }
 
   @Test
   public void find_users() {
-    when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(
+    when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(
       newArrayList(new UserWithPermissionDto().setName("user1").setPermission("user"))
       );
+    when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(1);
 
-    UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").build());
+    UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").build());
     assertThat(result.users()).hasSize(1);
-    assertThat(result.hasMoreResults()).isFalse();
+    assertThat(result.total()).isEqualTo(1);
   }
 
   @Test
@@ -84,7 +92,7 @@ public class PermissionFinderTest {
     when(resourceDao.selectResource(any(ResourceQuery.class))).thenReturn(null);
 
     try {
-      finder.findUsersWithPermission(PermissionQuery.builder().permission("user").component("Unknown").build());
+      underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").component("Unknown").build());
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Component 'Unknown' does not exist");
@@ -93,51 +101,54 @@ public class PermissionFinderTest {
 
   @Test
   public void find_users_with_paging() {
-    finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(3).pageSize(10).build());
+    underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(3).pageSize(10).build());
 
     ArgumentCaptor<Integer> argumentOffset = ArgumentCaptor.forClass(Integer.class);
     ArgumentCaptor<Integer> argumentLimit = ArgumentCaptor.forClass(Integer.class);
-    verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
+    verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
 
     assertThat(argumentOffset.getValue()).isEqualTo(20);
-    assertThat(argumentLimit.getValue()).isEqualTo(11);
+    assertThat(argumentLimit.getValue()).isEqualTo(10);
   }
 
   @Test
   public void find_users_with_paging_having_more_results() {
-    when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList(
+    when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList(
       new UserWithPermissionDto().setName("user1").setPermission("user"),
       new UserWithPermissionDto().setName("user2").setPermission("user"),
       new UserWithPermissionDto().setName("user3").setPermission("user"))
       );
-    UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(2).build());
+    when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(3);
+    UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(2).build());
 
     ArgumentCaptor<Integer> argumentOffset = ArgumentCaptor.forClass(Integer.class);
     ArgumentCaptor<Integer> argumentLimit = ArgumentCaptor.forClass(Integer.class);
-    verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
+    verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
 
     assertThat(argumentOffset.getValue()).isEqualTo(0);
-    assertThat(argumentLimit.getValue()).isEqualTo(3);
-    assertThat(result.hasMoreResults()).isTrue();
+    assertThat(argumentLimit.getValue()).isEqualTo(2);
+    assertThat(result.total()).isEqualTo(3);
   }
 
   @Test
   public void find_users_with_paging_having_no_more_results() {
-    when(permissionDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList(
+    when(permissionDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(newArrayList(
       new UserWithPermissionDto().setName("user1").setPermission("user"),
       new UserWithPermissionDto().setName("user2").setPermission("user"),
       new UserWithPermissionDto().setName("user4").setPermission("user"),
       new UserWithPermissionDto().setName("user3").setPermission("user"))
       );
-    UserWithPermissionQueryResult result = finder.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(10).build());
+    when(permissionDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(4);
+
+    UserWithPermissionQueryResult result = underTest.findUsersWithPermission(PermissionQuery.builder().permission("user").pageIndex(1).pageSize(10).build());
 
     ArgumentCaptor<Integer> argumentOffset = ArgumentCaptor.forClass(Integer.class);
     ArgumentCaptor<Integer> argumentLimit = ArgumentCaptor.forClass(Integer.class);
-    verify(permissionDao).selectUsers(any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
+    verify(permissionDao).selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), argumentOffset.capture(), argumentLimit.capture());
 
     assertThat(argumentOffset.getValue()).isEqualTo(0);
-    assertThat(argumentLimit.getValue()).isEqualTo(11);
-    assertThat(result.hasMoreResults()).isFalse();
+    assertThat(argumentLimit.getValue()).isEqualTo(10);
+    assertThat(result.total()).isEqualTo(4);
   }
 
   @Test
@@ -146,7 +157,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(
       PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build());
     assertThat(result.groups()).hasSize(1);
     assertThat(result.hasMoreResults()).isFalse();
@@ -162,7 +173,7 @@ public class PermissionFinderTest {
       new GroupWithPermissionDto().setName("Other").setPermission(null)
       ));
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(
       PermissionQuery.builder()
         .permission("user")
         .pageSize(2)
@@ -175,7 +186,7 @@ public class PermissionFinderTest {
     assertThat(groups.get(0).name()).isEqualTo("Users");
     assertThat(groups.get(1).name()).isEqualTo("Reviewers");
 
-    assertThat(finder.findGroupsWithPermission(
+    assertThat(underTest.findGroupsWithPermission(
       PermissionQuery.builder()
         .permission("user")
         .pageSize(2)
@@ -193,11 +204,11 @@ public class PermissionFinderTest {
       new GroupWithPermissionDto().setName("Other").setPermission(null)
       ));
 
-    assertThat(finder.findGroupsWithPermission(
+    assertThat(underTest.findGroupsWithPermission(
       PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build()).groups()).hasSize(2);
-    assertThat(finder.findGroupsWithPermission(
+    assertThat(underTest.findGroupsWithPermission(
       PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build()).groups()).hasSize(3);
-    assertThat(finder.findGroupsWithPermission(
+    assertThat(underTest.findGroupsWithPermission(
       PermissionQuery.builder().permission("user").membership(PermissionQuery.ANY).build()).groups()).hasSize(5);
   }
 
@@ -207,7 +218,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user")
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user")
       .pageIndex(1).membership(PermissionQuery.ANY).build());
     assertThat(result.groups()).hasSize(2);
     GroupWithPermission first = result.groups().get(0);
@@ -221,7 +232,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("other")
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("other")
       .pageIndex(1).membership(PermissionQuery.ANY).build());
     // Anyone group should not be added
     assertThat(result.groups()).hasSize(1);
@@ -233,7 +244,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("MyAnyGroup").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("any")
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user").search("any")
       .pageIndex(1).membership(PermissionQuery.ANY).build());
     assertThat(result.groups()).hasSize(2);
   }
@@ -244,7 +255,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermission(PermissionQuery.builder().permission("user")
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermission(PermissionQuery.builder().permission("user")
       .pageIndex(1).membership(PermissionQuery.OUT).build());
     // Anyone group should not be added
     assertThat(result.groups()).hasSize(1);
@@ -254,13 +265,15 @@ public class PermissionFinderTest {
   public void find_users_from_permission_template() {
     when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(new PermissionTemplateDto().setId(1L).setKee("my_template"));
 
-    when(permissionTemplateDao.selectUsers(any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(
+    when(permissionTemplateDao.selectUsers(any(DbSession.class), any(PermissionQuery.class), anyLong(), anyInt(), anyInt())).thenReturn(
       newArrayList(new UserWithPermissionDto().setName("user1").setPermission("user"))
       );
 
-    UserWithPermissionQueryResult result = finder.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("my_template").build());
+    when(permissionTemplateDao.countUsers(any(DbSession.class), any(PermissionQuery.class), anyLong())).thenReturn(1);
+
+    UserWithPermissionQueryResult result = underTest.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("my_template").build());
     assertThat(result.users()).hasSize(1);
-    assertThat(result.hasMoreResults()).isFalse();
+    assertThat(result.total()).isEqualTo(1);
   }
 
   @Test
@@ -268,7 +281,7 @@ public class PermissionFinderTest {
     when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(null);
 
     try {
-      finder.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build());
+      underTest.findUsersWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build());
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Template 'Unknown' does not exist");
@@ -283,7 +296,7 @@ public class PermissionFinderTest {
       newArrayList(new GroupWithPermissionDto().setName("users").setPermission("user"))
       );
 
-    GroupWithPermissionQueryResult result = finder.findGroupsWithPermissionTemplate(
+    GroupWithPermissionQueryResult result = underTest.findGroupsWithPermissionTemplate(
       PermissionQuery.builder().permission("user").template("my_template").membership(PermissionQuery.OUT).build());
     assertThat(result.groups()).hasSize(1);
     assertThat(result.hasMoreResults()).isFalse();
@@ -294,7 +307,7 @@ public class PermissionFinderTest {
     when(permissionTemplateDao.selectTemplateByKey(anyString())).thenReturn(null);
 
     try {
-      finder.findGroupsWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build());
+      underTest.findGroupsWithPermissionTemplate(PermissionQuery.builder().permission("user").template("Unknown").build());
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("Template 'Unknown' does not exist");
index 075a7e6ceaabf51d33a8d21c71d409da0732af1c..f85654d5c1f3f8befe2e8cf0583a7032c7007006 100644 (file)
@@ -30,6 +30,6 @@ public class PermissionsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new PermissionsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(7);
+    assertThat(container.size()).isEqualTo(8);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java
new file mode 100644 (file)
index 0000000..f3bb787
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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.permission.ws;
+
+import com.google.common.io.Resources;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.server.ws.WebService.SelectionMode;
+import org.sonar.api.utils.System2;
+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.user.UserDto;
+import org.sonar.db.user.UserRoleDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.permission.PermissionFinder;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.JsonAssert.assertJson;
+
+@Category(DbTests.class)
+public class UsersActionTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  DbClient dbClient = db.getDbClient();
+  DbSession dbSession = db.getSession();
+  WsActionTester ws;
+  PermissionFinder permissionFinder;
+
+  UsersAction underTest;
+
+  @Before
+  public void setUp() {
+    permissionFinder = new PermissionFinder(dbClient);
+    userSession.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    underTest = new UsersAction(userSession, permissionFinder);
+    ws = new WsActionTester(underTest);
+
+    UserDto user1 = dbClient.userDao().insert(dbSession, new UserDto()
+      .setActive(true)
+      .setLogin("login-1")
+      .setName("name-1"));
+    UserDto user2 = dbClient.userDao().insert(dbSession, new UserDto()
+      .setActive(true)
+      .setLogin("login-2")
+      .setName("name-2"));
+    UserDto user3 = dbClient.userDao().insert(dbSession, new UserDto()
+      .setActive(true)
+      .setLogin("login-3")
+      .setName("name-3"));
+    dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+      .setRole(GlobalPermissions.SCAN_EXECUTION)
+      .setUserId(user1.getId()));
+    dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+      .setRole(GlobalPermissions.SCAN_EXECUTION)
+      .setUserId(user2.getId()));
+    dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+      .setRole(GlobalPermissions.SYSTEM_ADMIN)
+      .setUserId(user3.getId()));
+    dbSession.commit();
+  }
+
+  @Test
+  public void search_for_users_with_one_permission() {
+    String result = ws.newRequest().setParam("permission", "scan").execute().getInput();
+
+    assertJson(result).isSimilarTo(Resources.getResource(getClass(), "UsersActionTest/users.json"));
+  }
+
+  @Test
+  public void search_for_users_with_response_example() {
+    db.truncateTables();
+    UserDto user1 = dbClient.userDao().insert(dbSession, new UserDto()
+      .setActive(true)
+      .setLogin("admin")
+      .setName("Administrator"));
+    UserDto user2 = dbClient.userDao().insert(dbSession, new UserDto()
+      .setActive(true)
+      .setLogin("george.orwell")
+      .setName("George Orwell"));
+    dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+      .setRole(GlobalPermissions.SCAN_EXECUTION)
+      .setUserId(user1.getId()));
+    dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto()
+      .setRole(GlobalPermissions.SCAN_EXECUTION)
+      .setUserId(user2.getId()));
+    dbSession.commit();
+
+    String result = ws.newRequest().setParam("permission", "scan").execute().getInput();
+
+    assertJson(result).isSimilarTo(Resources.getResource(getClass(), "users-example.json"));
+  }
+
+  @Test
+  public void search_for_users_with_query_as_a_parameter() {
+    String result = ws.newRequest()
+      .setParam("permission", "scan")
+      .setParam(Param.TEXT_QUERY, "ame-1")
+      .execute().getInput();
+
+    assertThat(result).contains("login-1")
+      .doesNotContain("login-2")
+      .doesNotContain("login-3");
+
+  }
+
+  @Test
+  public void search_for_users_with_select_as_a_parameter() {
+    String result = ws.newRequest()
+      .setParam("permission", "scan")
+      .setParam(Param.SELECTED, SelectionMode.ALL.value())
+      .execute().getInput();
+
+    assertThat(result).contains("login-1", "login-2", "login-3");
+  }
+
+  @Test
+  public void fail_if_permission_parameter_is_not_filled() {
+    expectedException.expect(IllegalArgumentException.class);
+
+    ws.newRequest().execute();
+  }
+
+  @Test
+  public void fail_if_insufficient_privileges() {
+    expectedException.expect(ForbiddenException.class);
+    userSession.login("login");
+
+    ws.newRequest()
+      .setParam("permission", GlobalPermissions.SYSTEM_ADMIN)
+      .execute();
+  }
+
+  @Test
+  public void fail_if_not_logged_in() {
+    expectedException.expect(UnauthorizedException.class);
+    userSession.anonymous();
+
+    ws.newRequest()
+      .setParam("permission", GlobalPermissions.SYSTEM_ADMIN)
+      .execute();
+  }
+}
index e76480462fca600833075c9d0c08ed18f12fb7a6..c0f59742750a2ba485e3ff6c6f4956220795c9a0 100644 (file)
@@ -42,5 +42,4 @@ public class TestResponse {
   public String getMediaType() {
     return dumbResponse.stream().mediaType();
   }
-
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json b/server/sonar-server/src/test/resources/org/sonar/server/permission/ws/UsersActionTest/users.json
new file mode 100644 (file)
index 0000000..a3ecb8c
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "users": [
+    {
+      "login": "login-1",
+      "name": "name-1",
+      "selected": true
+    },
+    {
+      "login": "login-2",
+      "name": "name-2",
+      "selected": true
+    }
+  ],
+  "paging": {
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  }
+}
index e18bdbbcb57a5dbd641c67ad5b70c2503cce45f5..ae1ae1b65daae1df36292da50addaf9786273776 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.db.permission;
 
-import com.google.common.annotations.VisibleForTesting;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
@@ -28,6 +27,7 @@ import org.apache.ibatis.session.RowBounds;
 import org.apache.ibatis.session.SqlSession;
 import org.sonar.api.security.DefaultGroups;
 import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
 import org.sonar.db.MyBatis;
 
 import static com.google.common.collect.Maps.newHashMap;
@@ -46,27 +46,29 @@ public class PermissionDao implements Dao {
   /**
    * @return a paginated list of users.
    */
-  public List<UserWithPermissionDto> selectUsers(PermissionQuery query, @Nullable Long componentId, int offset, int limit) {
-    SqlSession session = myBatis.openSession(false);
-    try {
-      Map<String, Object> params = newHashMap();
-      params.put(QUERY_PARAMETER, query);
-      params.put(COMPONENT_ID_PARAMETER, componentId);
+  public List<UserWithPermissionDto> selectUsers(DbSession session, PermissionQuery query, @Nullable Long componentId, int offset, int limit) {
+    Map<String, Object> params = usersParameters(query, componentId);
 
-      return mapper(session).selectUsers(params, new RowBounds(offset, limit));
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
+    return mapper(session).selectUsers(params, new RowBounds(offset, limit));
+  }
+
+  public int countUsers(DbSession session, PermissionQuery query, @Nullable Long componentId) {
+    Map<String, Object> params = usersParameters(query, componentId);
+
+    return mapper(session).countUsers(params);
   }
 
-  @VisibleForTesting
-  List<UserWithPermissionDto> selectUsers(PermissionQuery query, @Nullable Long componentId) {
-    return selectUsers(query, componentId, 0, Integer.MAX_VALUE);
+  private static Map<String, Object> usersParameters(PermissionQuery query, @Nullable Long componentId) {
+    Map<String, Object> params = newHashMap();
+    params.put(QUERY_PARAMETER, query);
+    params.put(COMPONENT_ID_PARAMETER, componentId);
+    return params;
   }
 
   /**
    * 'Anyone' group is not returned when it has not the asked permission.
    * Membership parameter from query is not taking into account in order to deal more easily with the 'Anyone' group
+   *
    * @return a non paginated list of groups.
    */
   public List<GroupWithPermissionDto> selectGroups(PermissionQuery query, @Nullable Long componentId) {
@@ -86,5 +88,4 @@ public class PermissionDao implements Dao {
   private PermissionMapper mapper(SqlSession session) {
     return session.getMapper(PermissionMapper.class);
   }
-
 }
index 08c7b3e07c06d219de4dd229f9c7557aa31a9c16..0bf57813c535c12a90e387523e952960ed5205ae 100644 (file)
@@ -29,4 +29,6 @@ public interface PermissionMapper {
   List<UserWithPermissionDto> selectUsers(Map<String, Object> parameters, RowBounds rowBounds);
 
   List<GroupWithPermissionDto> selectGroups(Map<String, Object> parameters);
+
+  int countUsers(Map<String, Object> parameters);
 }
index b595e1609ad35dbc8ccbd9b6fb2c6ca70f360d3d..30945f54e72b81e0687c4dd7d9caf64faff47fe5 100644 (file)
@@ -54,17 +54,31 @@ public class PermissionTemplateDao implements Dao {
    * @return a paginated list of users.
    */
   public List<UserWithPermissionDto> selectUsers(PermissionQuery query, Long templateId, int offset, int limit) {
-    SqlSession session = myBatis.openSession(false);
+    DbSession session = myBatis.openSession(false);
     try {
-      Map<String, Object> params = newHashMap();
-      params.put(QUERY_PARAMETER, query);
-      params.put(TEMPLATE_ID_PARAMETER, templateId);
-      return mapper(session).selectUsers(params, new RowBounds(offset, limit));
+      return selectUsers(session, query, templateId, offset, limit);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  /**
+   * @return a paginated list of users.
+   */
+  public List<UserWithPermissionDto> selectUsers(DbSession session, PermissionQuery query, Long templateId, int offset, int limit) {
+    Map<String, Object> params = newHashMap();
+    params.put(QUERY_PARAMETER, query);
+    params.put(TEMPLATE_ID_PARAMETER, templateId);
+    return mapper(session).selectUsers(params, new RowBounds(offset, limit));
+  }
+
+  public int countUsers(DbSession session, PermissionQuery query, Long templateId) {
+    Map<String, Object> params = newHashMap();
+    params.put(QUERY_PARAMETER, query);
+    params.put(TEMPLATE_ID_PARAMETER, templateId);
+    return mapper(session).countUsers(params);
+  }
+
   @VisibleForTesting
   List<UserWithPermissionDto> selectUsers(PermissionQuery query, Long templateId) {
     return selectUsers(query, templateId, 0, Integer.MAX_VALUE);
index 1277462b14d8c6692154e30dfcfb6539365d3834..be35219518e0c757208cdaa6b39cabc36e2a99e2 100644 (file)
@@ -58,4 +58,6 @@ public interface PermissionTemplateMapper {
   List<GroupWithPermissionDto> selectGroups(Map<String, Object> params);
 
   List<UserWithPermissionDto> selectUsers(Map<String, Object> params, RowBounds rowBounds);
+
+  int countUsers(Map<String, Object> params);
 }
index 79a705a44dcf8b375c196b93f490705142dc03d5..a2e01badf9bc050376170243da0548dd52af9ca1 100644 (file)
@@ -5,6 +5,16 @@
 
   <select id="selectUsers" parameterType="map" resultType="UserWithPermission">
     SELECT u.login as login, u.name as name, user_role.role as permission
+    <include refid="usersSelection"/>
+    ORDER BY u.name
+  </select>
+
+  <select id="countUsers" parameterType="map" resultType="int">
+    SELECT count(u.login)
+    <include refid="usersSelection"/>
+  </select>
+
+  <sql id="usersSelection">
     FROM users u
     LEFT JOIN user_roles user_role ON user_role.user_id=u.id
     AND user_role.role=#{query.permission}
@@ -28,8 +38,7 @@
         AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/')
       </if>
     </where>
-    ORDER BY u.name
-  </select>
+  </sql>
 
   <select id="selectGroups" parameterType="map" resultType="GroupWithPermission">
     SELECT name, description, permission FROM
index 95567f2e5c215664fea38c22a619d7001086f90e..5ca9eb2859fc89fbd07c14c925368e95e91e97ce 100644 (file)
 
   <select id="selectUsers" parameterType="map" resultType="UserWithPermission">
     SELECT u.login as login, u.name as name, ptu.permission_reference as permission
+    <include refid="usersSelection"/>
+    ORDER BY u.name
+  </select>
+
+  <select id="countUsers" parameterType="map" resultType="int">
+    SELECT count(u.login)
+    <include refid="usersSelection"/>
+  </select>
+
+  <sql id="usersSelection">
     FROM users u
     LEFT JOIN perm_templates_users ptu ON ptu.user_id=u.id
     AND ptu.permission_reference=#{query.permission}
@@ -86,8 +96,7 @@
         AND (UPPER(u.name) LIKE #{query.searchSql} ESCAPE '/')
       </if>
     </where>
-    ORDER BY u.name
-  </select>
+  </sql>
 
   <select id="selectGroups" parameterType="map" resultType="GroupWithPermission">
     SELECT name, description, permission FROM
index 8b05e0be65b637f7c1e24f9dc371e753c52c7384..2a39dd98ed0b5e78a02a20a13a7fe9f189fbdd64 100644 (file)
@@ -26,17 +26,20 @@ import java.util.Date;
 import java.util.List;
 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.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.MyBatis;
+import org.sonar.test.DbTests;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+@Category(DbTests.class)
 public class PermissionTemplateDaoTest {
 
   System2 system = mock(System2.class);
index b81eb7c248690622f17379f42b453dc15d040880..b3b8540663fc6ff83765713d04809c21ee3b723f 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.test.DbTests;
 
@@ -40,15 +41,16 @@ public class UserWithPermissionDaoTest {
 
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  DbSession session = dbTester.getSession();
 
-  PermissionDao dao = dbTester.getDbClient().permissionDao();
+  PermissionDao underTest = dbTester.getDbClient().permissionDao();
 
   @Test
   public void select_all_users_for_project_permission() {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
     PermissionQuery query = PermissionQuery.builder().permission("user").build();
-    List<UserWithPermissionDto> result = dao.selectUsers(query, COMPONENT_ID);
+    List<UserWithPermissionDto> result = selectUsers(session, query, COMPONENT_ID);
     assertThat(result).hasSize(3);
 
     UserWithPermissionDto user1 = result.get(0);
@@ -72,7 +74,7 @@ public class UserWithPermissionDaoTest {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
     PermissionQuery query = PermissionQuery.builder().permission("admin").build();
-    List<UserWithPermissionDto> result = dao.selectUsers(query, null);
+    List<UserWithPermissionDto> result = selectUsers(session, query, null);
     assertThat(result).hasSize(3);
 
     UserWithPermissionDto user1 = result.get(0);
@@ -93,7 +95,7 @@ public class UserWithPermissionDaoTest {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
     // user1 and user2 have permission user
-    assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build(), COMPONENT_ID)).hasSize(2);
+    assertThat(selectUsers(session, PermissionQuery.builder().permission("user").membership(PermissionQuery.IN).build(), COMPONENT_ID)).hasSize(2);
   }
 
   @Test
@@ -101,18 +103,18 @@ public class UserWithPermissionDaoTest {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
     // Only user3 has not the user permission
-    assertThat(dao.selectUsers(PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build(), COMPONENT_ID)).hasSize(1);
+    assertThat(selectUsers(session, PermissionQuery.builder().permission("user").membership(PermissionQuery.OUT).build(), COMPONENT_ID)).hasSize(1);
   }
 
   @Test
   public void search_by_user_name() {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
-    List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").search("SEr1").build(), COMPONENT_ID);
+    List<UserWithPermissionDto> result = selectUsers(session, PermissionQuery.builder().permission("user").search("SEr1").build(), COMPONENT_ID);
     assertThat(result).hasSize(1);
     assertThat(result.get(0).getName()).isEqualTo("User1");
 
-    result = dao.selectUsers(PermissionQuery.builder().permission("user").search("user").build(), COMPONENT_ID);
+    result = selectUsers(session, PermissionQuery.builder().permission("user").search("user").build(), COMPONENT_ID);
     assertThat(result).hasSize(3);
   }
 
@@ -121,7 +123,7 @@ public class UserWithPermissionDaoTest {
     dbTester.prepareDbUnit(getClass(), "select_only_enable_users.xml");
 
     PermissionQuery query = PermissionQuery.builder().permission("user").build();
-    List<UserWithPermissionDto> result = dao.selectUsers(query, COMPONENT_ID);
+    List<UserWithPermissionDto> result = selectUsers(session, query, COMPONENT_ID);
     assertThat(result).hasSize(3);
 
     // Disabled user should not be returned
@@ -137,7 +139,7 @@ public class UserWithPermissionDaoTest {
   public void should_be_sorted_by_user_name() {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions_should_be_sorted_by_user_name.xml");
 
-    List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID);
+    List<UserWithPermissionDto> result = selectUsers(session, PermissionQuery.builder().permission("user").build(), COMPONENT_ID);
     assertThat(result).hasSize(3);
     assertThat(result.get(0).getName()).isEqualTo("User1");
     assertThat(result.get(1).getName()).isEqualTo("User2");
@@ -148,19 +150,23 @@ public class UserWithPermissionDaoTest {
   public void should_be_paginated() {
     dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
 
-    List<UserWithPermissionDto> result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 0, 2);
+    List<UserWithPermissionDto> result = underTest.selectUsers(session, PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 0, 2);
     assertThat(result).hasSize(2);
     assertThat(result.get(0).getName()).isEqualTo("User1");
     assertThat(result.get(1).getName()).isEqualTo("User2");
 
-    result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 1, 2);
+    result = underTest.selectUsers(session, PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 1, 2);
     assertThat(result).hasSize(2);
     assertThat(result.get(0).getName()).isEqualTo("User2");
     assertThat(result.get(1).getName()).isEqualTo("User3");
 
-    result = dao.selectUsers(PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 2, 1);
+    result = underTest.selectUsers(session, PermissionQuery.builder().permission("user").build(), COMPONENT_ID, 2, 1);
     assertThat(result).hasSize(1);
     assertThat(result.get(0).getName()).isEqualTo("User3");
   }
 
+  private List<UserWithPermissionDto> selectUsers(DbSession session, PermissionQuery query, @Nullable Long componentId) {
+    return underTest.selectUsers(session, query, componentId, 0, Integer.MAX_VALUE);
+  }
+
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java b/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java
new file mode 100644 (file)
index 0000000..1dcb0fa
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.db.user;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.math.RandomUtils;
+
+public class UserTesting {
+
+  public static UserDto newUserDto() {
+    return new UserDto()
+      .setActive(RandomUtils.nextBoolean())
+      .setLogin(RandomStringUtils.randomAlphanumeric(30));
+  }
+}
index fa78cd4edfa1b5254ea581925def714efa0a9ce8..e6b049ed4feca76ef7c21c8425bdd10c5987aa46 100644 (file)
@@ -21,12 +21,10 @@ package org.sonar.api.server.ws;
 
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-
 import java.io.IOException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -36,18 +34,18 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
-
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.ExtensionPoint;
 import org.sonar.api.server.ServerSide;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * Defines a web service. Note that contrary to the deprecated {@link org.sonar.api.web.Webservice}
  * the ws is fully implemented in Java and does not require any Ruby on Rails code.
@@ -647,7 +645,7 @@ public interface WebService extends Definable<WebService.Context> {
     }
 
     public static SelectionMode fromParam(String paramValue) {
-      Preconditions.checkArgument(BY_VALUE.containsKey(paramValue));
+      checkArgument(BY_VALUE.containsKey(paramValue));
       return BY_VALUE.get(paramValue);
     }
 
diff --git a/sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java b/sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java
new file mode 100644 (file)
index 0000000..afdadda
--- /dev/null
@@ -0,0 +1,1655 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: ws-permissions.proto
+
+package org.sonarqube.ws;
+
+public final class Permissions {
+  private Permissions() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+  }
+  public interface UsersResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:sonarqube.ws.permissions.UsersResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User> 
+        getUsersList();
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    org.sonarqube.ws.Permissions.UsersResponse.User getUsers(int index);
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    int getUsersCount();
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    java.util.List<? extends org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder> 
+        getUsersOrBuilderList();
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder getUsersOrBuilder(
+        int index);
+
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    boolean hasPaging();
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    org.sonarqube.ws.Common.Paging getPaging();
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    org.sonarqube.ws.Common.PagingOrBuilder getPagingOrBuilder();
+  }
+  /**
+   * Protobuf type {@code sonarqube.ws.permissions.UsersResponse}
+   *
+   * <pre>
+   * WS api/permissions/users for internal use only
+   * </pre>
+   */
+  public static final class UsersResponse extends
+      com.google.protobuf.GeneratedMessage implements
+      // @@protoc_insertion_point(message_implements:sonarqube.ws.permissions.UsersResponse)
+      UsersResponseOrBuilder {
+    // Use UsersResponse.newBuilder() to construct.
+    private UsersResponse(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
+      super(builder);
+      this.unknownFields = builder.getUnknownFields();
+    }
+    private UsersResponse(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
+
+    private static final UsersResponse defaultInstance;
+    public static UsersResponse getDefaultInstance() {
+      return defaultInstance;
+    }
+
+    public UsersResponse getDefaultInstanceForType() {
+      return defaultInstance;
+    }
+
+    private final com.google.protobuf.UnknownFieldSet unknownFields;
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+        getUnknownFields() {
+      return this.unknownFields;
+    }
+    private UsersResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      initFields();
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownField(input, unknownFields,
+                                     extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                users_ = new java.util.ArrayList<org.sonarqube.ws.Permissions.UsersResponse.User>();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              users_.add(input.readMessage(org.sonarqube.ws.Permissions.UsersResponse.User.PARSER, extensionRegistry));
+              break;
+            }
+            case 18: {
+              org.sonarqube.ws.Common.Paging.Builder subBuilder = null;
+              if (((bitField0_ & 0x00000001) == 0x00000001)) {
+                subBuilder = paging_.toBuilder();
+              }
+              paging_ = input.readMessage(org.sonarqube.ws.Common.Paging.PARSER, extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(paging_);
+                paging_ = subBuilder.buildPartial();
+              }
+              bitField0_ |= 0x00000001;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e.getMessage()).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          users_ = java.util.Collections.unmodifiableList(users_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              org.sonarqube.ws.Permissions.UsersResponse.class, org.sonarqube.ws.Permissions.UsersResponse.Builder.class);
+    }
+
+    public static com.google.protobuf.Parser<UsersResponse> PARSER =
+        new com.google.protobuf.AbstractParser<UsersResponse>() {
+      public UsersResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new UsersResponse(input, extensionRegistry);
+      }
+    };
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<UsersResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public interface UserOrBuilder extends
+        // @@protoc_insertion_point(interface_extends:sonarqube.ws.permissions.UsersResponse.User)
+        com.google.protobuf.MessageOrBuilder {
+
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      boolean hasLogin();
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      java.lang.String getLogin();
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      com.google.protobuf.ByteString
+          getLoginBytes();
+
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      boolean hasName();
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      java.lang.String getName();
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      com.google.protobuf.ByteString
+          getNameBytes();
+
+      /**
+       * <code>optional bool selected = 4;</code>
+       */
+      boolean hasSelected();
+      /**
+       * <code>optional bool selected = 4;</code>
+       */
+      boolean getSelected();
+    }
+    /**
+     * Protobuf type {@code sonarqube.ws.permissions.UsersResponse.User}
+     */
+    public static final class User extends
+        com.google.protobuf.GeneratedMessage implements
+        // @@protoc_insertion_point(message_implements:sonarqube.ws.permissions.UsersResponse.User)
+        UserOrBuilder {
+      // Use User.newBuilder() to construct.
+      private User(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
+        super(builder);
+        this.unknownFields = builder.getUnknownFields();
+      }
+      private User(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
+
+      private static final User defaultInstance;
+      public static User getDefaultInstance() {
+        return defaultInstance;
+      }
+
+      public User getDefaultInstanceForType() {
+        return defaultInstance;
+      }
+
+      private final com.google.protobuf.UnknownFieldSet unknownFields;
+      @java.lang.Override
+      public final com.google.protobuf.UnknownFieldSet
+          getUnknownFields() {
+        return this.unknownFields;
+      }
+      private User(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        initFields();
+        int mutable_bitField0_ = 0;
+        com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+            com.google.protobuf.UnknownFieldSet.newBuilder();
+        try {
+          boolean done = false;
+          while (!done) {
+            int tag = input.readTag();
+            switch (tag) {
+              case 0:
+                done = true;
+                break;
+              default: {
+                if (!parseUnknownField(input, unknownFields,
+                                       extensionRegistry, tag)) {
+                  done = true;
+                }
+                break;
+              }
+              case 10: {
+                com.google.protobuf.ByteString bs = input.readBytes();
+                bitField0_ |= 0x00000001;
+                login_ = bs;
+                break;
+              }
+              case 18: {
+                com.google.protobuf.ByteString bs = input.readBytes();
+                bitField0_ |= 0x00000002;
+                name_ = bs;
+                break;
+              }
+              case 32: {
+                bitField0_ |= 0x00000004;
+                selected_ = input.readBool();
+                break;
+              }
+            }
+          }
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          throw e.setUnfinishedMessage(this);
+        } catch (java.io.IOException e) {
+          throw new com.google.protobuf.InvalidProtocolBufferException(
+              e.getMessage()).setUnfinishedMessage(this);
+        } finally {
+          this.unknownFields = unknownFields.build();
+          makeExtensionsImmutable();
+        }
+      }
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_User_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                org.sonarqube.ws.Permissions.UsersResponse.User.class, org.sonarqube.ws.Permissions.UsersResponse.User.Builder.class);
+      }
+
+      public static com.google.protobuf.Parser<User> PARSER =
+          new com.google.protobuf.AbstractParser<User>() {
+        public User parsePartialFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws com.google.protobuf.InvalidProtocolBufferException {
+          return new User(input, extensionRegistry);
+        }
+      };
+
+      @java.lang.Override
+      public com.google.protobuf.Parser<User> getParserForType() {
+        return PARSER;
+      }
+
+      private int bitField0_;
+      public static final int LOGIN_FIELD_NUMBER = 1;
+      private java.lang.Object login_;
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      public boolean hasLogin() {
+        return ((bitField0_ & 0x00000001) == 0x00000001);
+      }
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      public java.lang.String getLogin() {
+        java.lang.Object ref = login_;
+        if (ref instanceof java.lang.String) {
+          return (java.lang.String) ref;
+        } else {
+          com.google.protobuf.ByteString bs = 
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (bs.isValidUtf8()) {
+            login_ = s;
+          }
+          return s;
+        }
+      }
+      /**
+       * <code>optional string login = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getLoginBytes() {
+        java.lang.Object ref = login_;
+        if (ref instanceof java.lang.String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          login_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+
+      public static final int NAME_FIELD_NUMBER = 2;
+      private java.lang.Object name_;
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      public boolean hasName() {
+        return ((bitField0_ & 0x00000002) == 0x00000002);
+      }
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (ref instanceof java.lang.String) {
+          return (java.lang.String) ref;
+        } else {
+          com.google.protobuf.ByteString bs = 
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (bs.isValidUtf8()) {
+            name_ = s;
+          }
+          return s;
+        }
+      }
+      /**
+       * <code>optional string name = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof java.lang.String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+
+      public static final int SELECTED_FIELD_NUMBER = 4;
+      private boolean selected_;
+      /**
+       * <code>optional bool selected = 4;</code>
+       */
+      public boolean hasSelected() {
+        return ((bitField0_ & 0x00000004) == 0x00000004);
+      }
+      /**
+       * <code>optional bool selected = 4;</code>
+       */
+      public boolean getSelected() {
+        return selected_;
+      }
+
+      private void initFields() {
+        login_ = "";
+        name_ = "";
+        selected_ = false;
+      }
+      private byte memoizedIsInitialized = -1;
+      public final boolean isInitialized() {
+        byte isInitialized = memoizedIsInitialized;
+        if (isInitialized == 1) return true;
+        if (isInitialized == 0) return false;
+
+        memoizedIsInitialized = 1;
+        return true;
+      }
+
+      public void writeTo(com.google.protobuf.CodedOutputStream output)
+                          throws java.io.IOException {
+        getSerializedSize();
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          output.writeBytes(1, getLoginBytes());
+        }
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          output.writeBytes(2, getNameBytes());
+        }
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          output.writeBool(4, selected_);
+        }
+        getUnknownFields().writeTo(output);
+      }
+
+      private int memoizedSerializedSize = -1;
+      public int getSerializedSize() {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+
+        size = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeBytesSize(1, getLoginBytes());
+        }
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeBytesSize(2, getNameBytes());
+        }
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeBoolSize(4, selected_);
+        }
+        size += getUnknownFields().getSerializedSize();
+        memoizedSerializedSize = size;
+        return size;
+      }
+
+      private static final long serialVersionUID = 0L;
+      @java.lang.Override
+      protected java.lang.Object writeReplace()
+          throws java.io.ObjectStreamException {
+        return super.writeReplace();
+      }
+
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          com.google.protobuf.ByteString data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          com.google.protobuf.ByteString data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(byte[] data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          byte[] data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return PARSER.parseFrom(input);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return PARSER.parseFrom(input, extensionRegistry);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseDelimitedFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return PARSER.parseDelimitedFrom(input);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseDelimitedFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return PARSER.parseDelimitedFrom(input, extensionRegistry);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          com.google.protobuf.CodedInputStream input)
+          throws java.io.IOException {
+        return PARSER.parseFrom(input);
+      }
+      public static org.sonarqube.ws.Permissions.UsersResponse.User parseFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return PARSER.parseFrom(input, extensionRegistry);
+      }
+
+      public static Builder newBuilder() { return Builder.create(); }
+      public Builder newBuilderForType() { return newBuilder(); }
+      public static Builder newBuilder(org.sonarqube.ws.Permissions.UsersResponse.User prototype) {
+        return newBuilder().mergeFrom(prototype);
+      }
+      public Builder toBuilder() { return newBuilder(this); }
+
+      @java.lang.Override
+      protected Builder newBuilderForType(
+          com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+        Builder builder = new Builder(parent);
+        return builder;
+      }
+      /**
+       * Protobuf type {@code sonarqube.ws.permissions.UsersResponse.User}
+       */
+      public static final class Builder extends
+          com.google.protobuf.GeneratedMessage.Builder<Builder> implements
+          // @@protoc_insertion_point(builder_implements:sonarqube.ws.permissions.UsersResponse.User)
+          org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder {
+        public static final com.google.protobuf.Descriptors.Descriptor
+            getDescriptor() {
+          return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor;
+        }
+
+        protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+            internalGetFieldAccessorTable() {
+          return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_User_fieldAccessorTable
+              .ensureFieldAccessorsInitialized(
+                  org.sonarqube.ws.Permissions.UsersResponse.User.class, org.sonarqube.ws.Permissions.UsersResponse.User.Builder.class);
+        }
+
+        // Construct using org.sonarqube.ws.Permissions.UsersResponse.User.newBuilder()
+        private Builder() {
+          maybeForceBuilderInitialization();
+        }
+
+        private Builder(
+            com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+          super(parent);
+          maybeForceBuilderInitialization();
+        }
+        private void maybeForceBuilderInitialization() {
+          if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+          }
+        }
+        private static Builder create() {
+          return new Builder();
+        }
+
+        public Builder clear() {
+          super.clear();
+          login_ = "";
+          bitField0_ = (bitField0_ & ~0x00000001);
+          name_ = "";
+          bitField0_ = (bitField0_ & ~0x00000002);
+          selected_ = false;
+          bitField0_ = (bitField0_ & ~0x00000004);
+          return this;
+        }
+
+        public Builder clone() {
+          return create().mergeFrom(buildPartial());
+        }
+
+        public com.google.protobuf.Descriptors.Descriptor
+            getDescriptorForType() {
+          return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor;
+        }
+
+        public org.sonarqube.ws.Permissions.UsersResponse.User getDefaultInstanceForType() {
+          return org.sonarqube.ws.Permissions.UsersResponse.User.getDefaultInstance();
+        }
+
+        public org.sonarqube.ws.Permissions.UsersResponse.User build() {
+          org.sonarqube.ws.Permissions.UsersResponse.User result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(result);
+          }
+          return result;
+        }
+
+        public org.sonarqube.ws.Permissions.UsersResponse.User buildPartial() {
+          org.sonarqube.ws.Permissions.UsersResponse.User result = new org.sonarqube.ws.Permissions.UsersResponse.User(this);
+          int from_bitField0_ = bitField0_;
+          int to_bitField0_ = 0;
+          if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+            to_bitField0_ |= 0x00000001;
+          }
+          result.login_ = login_;
+          if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+            to_bitField0_ |= 0x00000002;
+          }
+          result.name_ = name_;
+          if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+            to_bitField0_ |= 0x00000004;
+          }
+          result.selected_ = selected_;
+          result.bitField0_ = to_bitField0_;
+          onBuilt();
+          return result;
+        }
+
+        public Builder mergeFrom(com.google.protobuf.Message other) {
+          if (other instanceof org.sonarqube.ws.Permissions.UsersResponse.User) {
+            return mergeFrom((org.sonarqube.ws.Permissions.UsersResponse.User)other);
+          } else {
+            super.mergeFrom(other);
+            return this;
+          }
+        }
+
+        public Builder mergeFrom(org.sonarqube.ws.Permissions.UsersResponse.User other) {
+          if (other == org.sonarqube.ws.Permissions.UsersResponse.User.getDefaultInstance()) return this;
+          if (other.hasLogin()) {
+            bitField0_ |= 0x00000001;
+            login_ = other.login_;
+            onChanged();
+          }
+          if (other.hasName()) {
+            bitField0_ |= 0x00000002;
+            name_ = other.name_;
+            onChanged();
+          }
+          if (other.hasSelected()) {
+            setSelected(other.getSelected());
+          }
+          this.mergeUnknownFields(other.getUnknownFields());
+          return this;
+        }
+
+        public final boolean isInitialized() {
+          return true;
+        }
+
+        public Builder mergeFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws java.io.IOException {
+          org.sonarqube.ws.Permissions.UsersResponse.User parsedMessage = null;
+          try {
+            parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+          } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            parsedMessage = (org.sonarqube.ws.Permissions.UsersResponse.User) e.getUnfinishedMessage();
+            throw e;
+          } finally {
+            if (parsedMessage != null) {
+              mergeFrom(parsedMessage);
+            }
+          }
+          return this;
+        }
+        private int bitField0_;
+
+        private java.lang.Object login_ = "";
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public boolean hasLogin() {
+          return ((bitField0_ & 0x00000001) == 0x00000001);
+        }
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public java.lang.String getLogin() {
+          java.lang.Object ref = login_;
+          if (!(ref instanceof java.lang.String)) {
+            com.google.protobuf.ByteString bs =
+                (com.google.protobuf.ByteString) ref;
+            java.lang.String s = bs.toStringUtf8();
+            if (bs.isValidUtf8()) {
+              login_ = s;
+            }
+            return s;
+          } else {
+            return (java.lang.String) ref;
+          }
+        }
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public com.google.protobuf.ByteString
+            getLoginBytes() {
+          java.lang.Object ref = login_;
+          if (ref instanceof String) {
+            com.google.protobuf.ByteString b = 
+                com.google.protobuf.ByteString.copyFromUtf8(
+                    (java.lang.String) ref);
+            login_ = b;
+            return b;
+          } else {
+            return (com.google.protobuf.ByteString) ref;
+          }
+        }
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public Builder setLogin(
+            java.lang.String value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+          login_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public Builder clearLogin() {
+          bitField0_ = (bitField0_ & ~0x00000001);
+          login_ = getDefaultInstance().getLogin();
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional string login = 1;</code>
+         */
+        public Builder setLoginBytes(
+            com.google.protobuf.ByteString value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+          login_ = value;
+          onChanged();
+          return this;
+        }
+
+        private java.lang.Object name_ = "";
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public boolean hasName() {
+          return ((bitField0_ & 0x00000002) == 0x00000002);
+        }
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public java.lang.String getName() {
+          java.lang.Object ref = name_;
+          if (!(ref instanceof java.lang.String)) {
+            com.google.protobuf.ByteString bs =
+                (com.google.protobuf.ByteString) ref;
+            java.lang.String s = bs.toStringUtf8();
+            if (bs.isValidUtf8()) {
+              name_ = s;
+            }
+            return s;
+          } else {
+            return (java.lang.String) ref;
+          }
+        }
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public com.google.protobuf.ByteString
+            getNameBytes() {
+          java.lang.Object ref = name_;
+          if (ref instanceof String) {
+            com.google.protobuf.ByteString b = 
+                com.google.protobuf.ByteString.copyFromUtf8(
+                    (java.lang.String) ref);
+            name_ = b;
+            return b;
+          } else {
+            return (com.google.protobuf.ByteString) ref;
+          }
+        }
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public Builder setName(
+            java.lang.String value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000002;
+          name_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public Builder clearName() {
+          bitField0_ = (bitField0_ & ~0x00000002);
+          name_ = getDefaultInstance().getName();
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional string name = 2;</code>
+         */
+        public Builder setNameBytes(
+            com.google.protobuf.ByteString value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000002;
+          name_ = value;
+          onChanged();
+          return this;
+        }
+
+        private boolean selected_ ;
+        /**
+         * <code>optional bool selected = 4;</code>
+         */
+        public boolean hasSelected() {
+          return ((bitField0_ & 0x00000004) == 0x00000004);
+        }
+        /**
+         * <code>optional bool selected = 4;</code>
+         */
+        public boolean getSelected() {
+          return selected_;
+        }
+        /**
+         * <code>optional bool selected = 4;</code>
+         */
+        public Builder setSelected(boolean value) {
+          bitField0_ |= 0x00000004;
+          selected_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional bool selected = 4;</code>
+         */
+        public Builder clearSelected() {
+          bitField0_ = (bitField0_ & ~0x00000004);
+          selected_ = false;
+          onChanged();
+          return this;
+        }
+
+        // @@protoc_insertion_point(builder_scope:sonarqube.ws.permissions.UsersResponse.User)
+      }
+
+      static {
+        defaultInstance = new User(true);
+        defaultInstance.initFields();
+      }
+
+      // @@protoc_insertion_point(class_scope:sonarqube.ws.permissions.UsersResponse.User)
+    }
+
+    private int bitField0_;
+    public static final int USERS_FIELD_NUMBER = 1;
+    private java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User> users_;
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    public java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User> getUsersList() {
+      return users_;
+    }
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    public java.util.List<? extends org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder> 
+        getUsersOrBuilderList() {
+      return users_;
+    }
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    public int getUsersCount() {
+      return users_.size();
+    }
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    public org.sonarqube.ws.Permissions.UsersResponse.User getUsers(int index) {
+      return users_.get(index);
+    }
+    /**
+     * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+     */
+    public org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder getUsersOrBuilder(
+        int index) {
+      return users_.get(index);
+    }
+
+    public static final int PAGING_FIELD_NUMBER = 2;
+    private org.sonarqube.ws.Common.Paging paging_;
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    public boolean hasPaging() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    public org.sonarqube.ws.Common.Paging getPaging() {
+      return paging_;
+    }
+    /**
+     * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+     */
+    public org.sonarqube.ws.Common.PagingOrBuilder getPagingOrBuilder() {
+      return paging_;
+    }
+
+    private void initFields() {
+      users_ = java.util.Collections.emptyList();
+      paging_ = org.sonarqube.ws.Common.Paging.getDefaultInstance();
+    }
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      for (int i = 0; i < users_.size(); i++) {
+        output.writeMessage(1, users_.get(i));
+      }
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        output.writeMessage(2, paging_);
+      }
+      getUnknownFields().writeTo(output);
+    }
+
+    private int memoizedSerializedSize = -1;
+    public int getSerializedSize() {
+      int size = memoizedSerializedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      for (int i = 0; i < users_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, users_.get(i));
+      }
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, paging_);
+      }
+      size += getUnknownFields().getSerializedSize();
+      memoizedSerializedSize = size;
+      return size;
+    }
+
+    private static final long serialVersionUID = 0L;
+    @java.lang.Override
+    protected java.lang.Object writeReplace()
+        throws java.io.ObjectStreamException {
+      return super.writeReplace();
+    }
+
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input, extensionRegistry);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return PARSER.parseDelimitedFrom(input);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseDelimitedFrom(input, extensionRegistry);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input);
+    }
+    public static org.sonarqube.ws.Permissions.UsersResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input, extensionRegistry);
+    }
+
+    public static Builder newBuilder() { return Builder.create(); }
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder(org.sonarqube.ws.Permissions.UsersResponse prototype) {
+      return newBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() { return newBuilder(this); }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code sonarqube.ws.permissions.UsersResponse}
+     *
+     * <pre>
+     * WS api/permissions/users for internal use only
+     * </pre>
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessage.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:sonarqube.ws.permissions.UsersResponse)
+        org.sonarqube.ws.Permissions.UsersResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                org.sonarqube.ws.Permissions.UsersResponse.class, org.sonarqube.ws.Permissions.UsersResponse.Builder.class);
+      }
+
+      // Construct using org.sonarqube.ws.Permissions.UsersResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+          getUsersFieldBuilder();
+          getPagingFieldBuilder();
+        }
+      }
+      private static Builder create() {
+        return new Builder();
+      }
+
+      public Builder clear() {
+        super.clear();
+        if (usersBuilder_ == null) {
+          users_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        } else {
+          usersBuilder_.clear();
+        }
+        if (pagingBuilder_ == null) {
+          paging_ = org.sonarqube.ws.Common.Paging.getDefaultInstance();
+        } else {
+          pagingBuilder_.clear();
+        }
+        bitField0_ = (bitField0_ & ~0x00000002);
+        return this;
+      }
+
+      public Builder clone() {
+        return create().mergeFrom(buildPartial());
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return org.sonarqube.ws.Permissions.internal_static_sonarqube_ws_permissions_UsersResponse_descriptor;
+      }
+
+      public org.sonarqube.ws.Permissions.UsersResponse getDefaultInstanceForType() {
+        return org.sonarqube.ws.Permissions.UsersResponse.getDefaultInstance();
+      }
+
+      public org.sonarqube.ws.Permissions.UsersResponse build() {
+        org.sonarqube.ws.Permissions.UsersResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public org.sonarqube.ws.Permissions.UsersResponse buildPartial() {
+        org.sonarqube.ws.Permissions.UsersResponse result = new org.sonarqube.ws.Permissions.UsersResponse(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (usersBuilder_ == null) {
+          if (((bitField0_ & 0x00000001) == 0x00000001)) {
+            users_ = java.util.Collections.unmodifiableList(users_);
+            bitField0_ = (bitField0_ & ~0x00000001);
+          }
+          result.users_ = users_;
+        } else {
+          result.users_ = usersBuilder_.build();
+        }
+        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+          to_bitField0_ |= 0x00000001;
+        }
+        if (pagingBuilder_ == null) {
+          result.paging_ = paging_;
+        } else {
+          result.paging_ = pagingBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof org.sonarqube.ws.Permissions.UsersResponse) {
+          return mergeFrom((org.sonarqube.ws.Permissions.UsersResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(org.sonarqube.ws.Permissions.UsersResponse other) {
+        if (other == org.sonarqube.ws.Permissions.UsersResponse.getDefaultInstance()) return this;
+        if (usersBuilder_ == null) {
+          if (!other.users_.isEmpty()) {
+            if (users_.isEmpty()) {
+              users_ = other.users_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+            } else {
+              ensureUsersIsMutable();
+              users_.addAll(other.users_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.users_.isEmpty()) {
+            if (usersBuilder_.isEmpty()) {
+              usersBuilder_.dispose();
+              usersBuilder_ = null;
+              users_ = other.users_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+              usersBuilder_ = 
+                com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ?
+                   getUsersFieldBuilder() : null;
+            } else {
+              usersBuilder_.addAllMessages(other.users_);
+            }
+          }
+        }
+        if (other.hasPaging()) {
+          mergePaging(other.getPaging());
+        }
+        this.mergeUnknownFields(other.getUnknownFields());
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        org.sonarqube.ws.Permissions.UsersResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (org.sonarqube.ws.Permissions.UsersResponse) e.getUnfinishedMessage();
+          throw e;
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User> users_ =
+        java.util.Collections.emptyList();
+      private void ensureUsersIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          users_ = new java.util.ArrayList<org.sonarqube.ws.Permissions.UsersResponse.User>(users_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilder<
+          org.sonarqube.ws.Permissions.UsersResponse.User, org.sonarqube.ws.Permissions.UsersResponse.User.Builder, org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder> usersBuilder_;
+
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User> getUsersList() {
+        if (usersBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(users_);
+        } else {
+          return usersBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public int getUsersCount() {
+        if (usersBuilder_ == null) {
+          return users_.size();
+        } else {
+          return usersBuilder_.getCount();
+        }
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public org.sonarqube.ws.Permissions.UsersResponse.User getUsers(int index) {
+        if (usersBuilder_ == null) {
+          return users_.get(index);
+        } else {
+          return usersBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder setUsers(
+          int index, org.sonarqube.ws.Permissions.UsersResponse.User value) {
+        if (usersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureUsersIsMutable();
+          users_.set(index, value);
+          onChanged();
+        } else {
+          usersBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder setUsers(
+          int index, org.sonarqube.ws.Permissions.UsersResponse.User.Builder builderForValue) {
+        if (usersBuilder_ == null) {
+          ensureUsersIsMutable();
+          users_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          usersBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder addUsers(org.sonarqube.ws.Permissions.UsersResponse.User value) {
+        if (usersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureUsersIsMutable();
+          users_.add(value);
+          onChanged();
+        } else {
+          usersBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder addUsers(
+          int index, org.sonarqube.ws.Permissions.UsersResponse.User value) {
+        if (usersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureUsersIsMutable();
+          users_.add(index, value);
+          onChanged();
+        } else {
+          usersBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder addUsers(
+          org.sonarqube.ws.Permissions.UsersResponse.User.Builder builderForValue) {
+        if (usersBuilder_ == null) {
+          ensureUsersIsMutable();
+          users_.add(builderForValue.build());
+          onChanged();
+        } else {
+          usersBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder addUsers(
+          int index, org.sonarqube.ws.Permissions.UsersResponse.User.Builder builderForValue) {
+        if (usersBuilder_ == null) {
+          ensureUsersIsMutable();
+          users_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          usersBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder addAllUsers(
+          java.lang.Iterable<? extends org.sonarqube.ws.Permissions.UsersResponse.User> values) {
+        if (usersBuilder_ == null) {
+          ensureUsersIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, users_);
+          onChanged();
+        } else {
+          usersBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder clearUsers() {
+        if (usersBuilder_ == null) {
+          users_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+          onChanged();
+        } else {
+          usersBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public Builder removeUsers(int index) {
+        if (usersBuilder_ == null) {
+          ensureUsersIsMutable();
+          users_.remove(index);
+          onChanged();
+        } else {
+          usersBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public org.sonarqube.ws.Permissions.UsersResponse.User.Builder getUsersBuilder(
+          int index) {
+        return getUsersFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder getUsersOrBuilder(
+          int index) {
+        if (usersBuilder_ == null) {
+          return users_.get(index);  } else {
+          return usersBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public java.util.List<? extends org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder> 
+           getUsersOrBuilderList() {
+        if (usersBuilder_ != null) {
+          return usersBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(users_);
+        }
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public org.sonarqube.ws.Permissions.UsersResponse.User.Builder addUsersBuilder() {
+        return getUsersFieldBuilder().addBuilder(
+            org.sonarqube.ws.Permissions.UsersResponse.User.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public org.sonarqube.ws.Permissions.UsersResponse.User.Builder addUsersBuilder(
+          int index) {
+        return getUsersFieldBuilder().addBuilder(
+            index, org.sonarqube.ws.Permissions.UsersResponse.User.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .sonarqube.ws.permissions.UsersResponse.User users = 1;</code>
+       */
+      public java.util.List<org.sonarqube.ws.Permissions.UsersResponse.User.Builder> 
+           getUsersBuilderList() {
+        return getUsersFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilder<
+          org.sonarqube.ws.Permissions.UsersResponse.User, org.sonarqube.ws.Permissions.UsersResponse.User.Builder, org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder> 
+          getUsersFieldBuilder() {
+        if (usersBuilder_ == null) {
+          usersBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
+              org.sonarqube.ws.Permissions.UsersResponse.User, org.sonarqube.ws.Permissions.UsersResponse.User.Builder, org.sonarqube.ws.Permissions.UsersResponse.UserOrBuilder>(
+                  users_,
+                  ((bitField0_ & 0x00000001) == 0x00000001),
+                  getParentForChildren(),
+                  isClean());
+          users_ = null;
+        }
+        return usersBuilder_;
+      }
+
+      private org.sonarqube.ws.Common.Paging paging_ = org.sonarqube.ws.Common.Paging.getDefaultInstance();
+      private com.google.protobuf.SingleFieldBuilder<
+          org.sonarqube.ws.Common.Paging, org.sonarqube.ws.Common.Paging.Builder, org.sonarqube.ws.Common.PagingOrBuilder> pagingBuilder_;
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public boolean hasPaging() {
+        return ((bitField0_ & 0x00000002) == 0x00000002);
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public org.sonarqube.ws.Common.Paging getPaging() {
+        if (pagingBuilder_ == null) {
+          return paging_;
+        } else {
+          return pagingBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public Builder setPaging(org.sonarqube.ws.Common.Paging value) {
+        if (pagingBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          paging_ = value;
+          onChanged();
+        } else {
+          pagingBuilder_.setMessage(value);
+        }
+        bitField0_ |= 0x00000002;
+        return this;
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public Builder setPaging(
+          org.sonarqube.ws.Common.Paging.Builder builderForValue) {
+        if (pagingBuilder_ == null) {
+          paging_ = builderForValue.build();
+          onChanged();
+        } else {
+          pagingBuilder_.setMessage(builderForValue.build());
+        }
+        bitField0_ |= 0x00000002;
+        return this;
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public Builder mergePaging(org.sonarqube.ws.Common.Paging value) {
+        if (pagingBuilder_ == null) {
+          if (((bitField0_ & 0x00000002) == 0x00000002) &&
+              paging_ != org.sonarqube.ws.Common.Paging.getDefaultInstance()) {
+            paging_ =
+              org.sonarqube.ws.Common.Paging.newBuilder(paging_).mergeFrom(value).buildPartial();
+          } else {
+            paging_ = value;
+          }
+          onChanged();
+        } else {
+          pagingBuilder_.mergeFrom(value);
+        }
+        bitField0_ |= 0x00000002;
+        return this;
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public Builder clearPaging() {
+        if (pagingBuilder_ == null) {
+          paging_ = org.sonarqube.ws.Common.Paging.getDefaultInstance();
+          onChanged();
+        } else {
+          pagingBuilder_.clear();
+        }
+        bitField0_ = (bitField0_ & ~0x00000002);
+        return this;
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public org.sonarqube.ws.Common.Paging.Builder getPagingBuilder() {
+        bitField0_ |= 0x00000002;
+        onChanged();
+        return getPagingFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      public org.sonarqube.ws.Common.PagingOrBuilder getPagingOrBuilder() {
+        if (pagingBuilder_ != null) {
+          return pagingBuilder_.getMessageOrBuilder();
+        } else {
+          return paging_;
+        }
+      }
+      /**
+       * <code>optional .sonarqube.ws.commons.Paging paging = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilder<
+          org.sonarqube.ws.Common.Paging, org.sonarqube.ws.Common.Paging.Builder, org.sonarqube.ws.Common.PagingOrBuilder> 
+          getPagingFieldBuilder() {
+        if (pagingBuilder_ == null) {
+          pagingBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+              org.sonarqube.ws.Common.Paging, org.sonarqube.ws.Common.Paging.Builder, org.sonarqube.ws.Common.PagingOrBuilder>(
+                  getPaging(),
+                  getParentForChildren(),
+                  isClean());
+          paging_ = null;
+        }
+        return pagingBuilder_;
+      }
+
+      // @@protoc_insertion_point(builder_scope:sonarqube.ws.permissions.UsersResponse)
+    }
+
+    static {
+      defaultInstance = new UsersResponse(true);
+      defaultInstance.initFields();
+    }
+
+    // @@protoc_insertion_point(class_scope:sonarqube.ws.permissions.UsersResponse)
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_sonarqube_ws_permissions_UsersResponse_descriptor;
+  private static
+    com.google.protobuf.GeneratedMessage.FieldAccessorTable
+      internal_static_sonarqube_ws_permissions_UsersResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor;
+  private static
+    com.google.protobuf.GeneratedMessage.FieldAccessorTable
+      internal_static_sonarqube_ws_permissions_UsersResponse_User_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\024ws-permissions.proto\022\030sonarqube.ws.per" +
+      "missions\032\020ws-commons.proto\"\261\001\n\rUsersResp" +
+      "onse\022;\n\005users\030\001 \003(\0132,.sonarqube.ws.permi" +
+      "ssions.UsersResponse.User\022,\n\006paging\030\002 \001(" +
+      "\0132\034.sonarqube.ws.commons.Paging\0325\n\004User\022" +
+      "\r\n\005login\030\001 \001(\t\022\014\n\004name\030\002 \001(\t\022\020\n\010selected" +
+      "\030\004 \001(\010B!\n\020org.sonarqube.wsB\013PermissionsH" +
+      "\001"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          org.sonarqube.ws.Common.getDescriptor(),
+        }, assigner);
+    internal_static_sonarqube_ws_permissions_UsersResponse_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_sonarqube_ws_permissions_UsersResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+        internal_static_sonarqube_ws_permissions_UsersResponse_descriptor,
+        new java.lang.String[] { "Users", "Paging", });
+    internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor =
+      internal_static_sonarqube_ws_permissions_UsersResponse_descriptor.getNestedTypes().get(0);
+    internal_static_sonarqube_ws_permissions_UsersResponse_User_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+        internal_static_sonarqube_ws_permissions_UsersResponse_User_descriptor,
+        new java.lang.String[] { "Login", "Name", "Selected", });
+    org.sonarqube.ws.Common.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/sonar-ws/src/main/protobuf/ws-permissions.proto b/sonar-ws/src/main/protobuf/ws-permissions.proto
new file mode 100644 (file)
index 0000000..f68c26c
--- /dev/null
@@ -0,0 +1,40 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2015 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.
+
+syntax = "proto2";
+
+package sonarqube.ws.permissions;
+
+import "ws-commons.proto";
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "Permissions";
+option optimize_for = SPEED;
+
+// WS api/permissions/users for internal use only
+message UsersResponse {
+
+  message User {
+    optional string login = 1;
+    optional string name = 2;
+    optional bool selected = 4;
+  }
+
+  repeated User users = 1;
+  optional sonarqube.ws.commons.Paging paging = 2;
+}