3 * Copyright (C) 2009-2021 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.permission.ws;
22 import com.google.common.collect.Collections2;
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Table;
25 import com.google.common.collect.TreeBasedTable;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Optional;
29 import javax.annotation.CheckForNull;
30 import javax.annotation.Nullable;
31 import org.sonar.api.resources.ResourceType;
32 import org.sonar.api.resources.ResourceTypes;
33 import org.sonar.api.server.ws.Request;
34 import org.sonar.api.server.ws.Response;
35 import org.sonar.api.server.ws.WebService;
36 import org.sonar.api.server.ws.WebService.Param;
37 import org.sonar.api.utils.Paging;
38 import org.sonar.core.i18n.I18n;
39 import org.sonar.db.DbClient;
40 import org.sonar.db.DbSession;
41 import org.sonar.db.component.ComponentDto;
42 import org.sonar.db.component.ComponentQuery;
43 import org.sonar.db.permission.CountPerProjectPermission;
44 import org.sonar.server.permission.PermissionService;
45 import org.sonar.server.permission.RequestValidator;
46 import org.sonar.server.user.UserSession;
47 import org.sonarqube.ws.Common;
48 import org.sonarqube.ws.Permissions.Permission;
49 import org.sonarqube.ws.Permissions.SearchProjectPermissionsWsResponse;
50 import org.sonarqube.ws.Permissions.SearchProjectPermissionsWsResponse.Project;
52 import static java.util.Collections.singletonList;
53 import static org.sonar.api.utils.Paging.forPageIndex;
54 import static org.sonar.server.permission.ws.ProjectWsRef.newOptionalWsProjectRef;
55 import static org.sonar.server.permission.ws.SearchProjectPermissionsData.newBuilder;
56 import static org.sonar.server.permission.ws.WsParameters.createProjectParameters;
57 import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
58 import static org.sonar.server.ws.WsParameterBuilder.createRootQualifierParameter;
59 import static org.sonar.server.ws.WsUtils.writeProtobuf;
60 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
61 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
62 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_QUALIFIER;
64 public class SearchProjectPermissionsAction implements PermissionsWsAction {
65 private static final String PROPERTY_PREFIX = "projects_role.";
66 private static final String DESCRIPTION_SUFFIX = ".desc";
68 private final DbClient dbClient;
69 private final UserSession userSession;
70 private final I18n i18n;
71 private final ResourceTypes resourceTypes;
72 private final PermissionWsSupport wsSupport;
73 private final String[] rootQualifiers;
74 private final PermissionService permissionService;
76 public SearchProjectPermissionsAction(DbClient dbClient, UserSession userSession, I18n i18n, ResourceTypes resourceTypes,
77 PermissionWsSupport wsSupport, PermissionService permissionService) {
78 this.dbClient = dbClient;
79 this.userSession = userSession;
81 this.resourceTypes = resourceTypes;
82 this.wsSupport = wsSupport;
83 this.rootQualifiers = Collections2.transform(resourceTypes.getRoots(), ResourceType::getQualifier).toArray(new String[resourceTypes.getRoots().size()]);
84 this.permissionService = permissionService;
88 public void define(WebService.NewController context) {
89 WebService.NewAction action = context.createAction("search_project_permissions")
90 .setDescription("List project permissions. A project can be a technical project, a view or a developer.<br />" +
91 "Requires one of the following permissions:" +
93 "<li>'Administer System'</li>" +
94 "<li>'Administer' rights on the specified project</li>" +
96 .setResponseExample(getClass().getResource("search_project_permissions-example.json"))
98 .setDeprecatedSince("6.5")
102 action.createParam(Param.TEXT_QUERY)
103 .setDescription("Limit search to: <ul>" +
104 "<li>project names that contain the supplied string</li>" +
105 "<li>project keys that are exactly the same as the supplied string</li>" +
107 .setExampleValue("apac");
108 createProjectParameters(action);
109 createRootQualifierParameter(action, newQualifierParameterContext(i18n, resourceTypes))
114 public void handle(Request wsRequest, Response wsResponse) throws Exception {
115 SearchProjectPermissionsWsResponse searchProjectPermissionsWsResponse = doHandle(toSearchProjectPermissionsWsRequest(wsRequest));
116 writeProtobuf(searchProjectPermissionsWsResponse, wsRequest, wsResponse);
119 private SearchProjectPermissionsWsResponse doHandle(SearchProjectPermissionsRequest request) {
120 try (DbSession dbSession = dbClient.openSession(false)) {
121 checkAuthorized(dbSession, request);
122 RequestValidator.validateQualifier(request.getQualifier(), resourceTypes);
123 SearchProjectPermissionsData data = load(dbSession, request);
124 return buildResponse(data);
128 private static SearchProjectPermissionsRequest toSearchProjectPermissionsWsRequest(Request request) {
129 return new SearchProjectPermissionsRequest()
130 .setProjectId(request.param(PARAM_PROJECT_ID))
131 .setProjectKey(request.param(PARAM_PROJECT_KEY))
132 .setQualifier(request.param(PARAM_QUALIFIER))
133 .setPage(request.mandatoryParamAsInt(Param.PAGE))
134 .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
135 .setQuery(request.param(Param.TEXT_QUERY));
138 private void checkAuthorized(DbSession dbSession, SearchProjectPermissionsRequest request) {
139 Optional<ProjectWsRef> projectRef = newOptionalWsProjectRef(request.getProjectId(), request.getProjectKey());
140 if (projectRef.isPresent()) {
141 ComponentDto project = wsSupport.getRootComponentOrModule(dbSession, projectRef.get());
142 wsSupport.checkPermissionManagementAccess(userSession, project);
144 userSession.checkLoggedIn().checkIsSystemAdministrator();
148 private SearchProjectPermissionsWsResponse buildResponse(SearchProjectPermissionsData data) {
149 SearchProjectPermissionsWsResponse.Builder response = SearchProjectPermissionsWsResponse.newBuilder();
150 Permission.Builder permissionResponse = Permission.newBuilder();
152 Project.Builder rootComponentBuilder = Project.newBuilder();
153 for (ComponentDto rootComponent : data.rootComponents()) {
156 .setId(rootComponent.uuid())
157 .setKey(rootComponent.getDbKey())
158 .setQualifier(rootComponent.qualifier())
159 .setName(rootComponent.name());
160 for (String permission : data.permissions(rootComponent.uuid())) {
161 rootComponentBuilder.addPermissions(
165 .setUsersCount(data.userCount(rootComponent.uuid(), permission))
166 .setGroupsCount(data.groupCount(rootComponent.uuid(), permission)));
168 response.addProjects(rootComponentBuilder);
171 for (String permissionKey : permissionService.getAllProjectPermissions()) {
172 response.addPermissions(
175 .setKey(permissionKey)
176 .setName(i18nName(permissionKey))
177 .setDescription(i18nDescriptionMessage(permissionKey)));
180 Paging paging = data.paging();
182 Common.Paging.newBuilder()
183 .setPageIndex(paging.pageIndex())
184 .setPageSize(paging.pageSize())
185 .setTotal(paging.total()));
187 return response.build();
190 private String i18nDescriptionMessage(String permissionKey) {
191 return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey + DESCRIPTION_SUFFIX, "");
194 private String i18nName(String permissionKey) {
195 return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey, permissionKey);
198 private SearchProjectPermissionsData load(DbSession dbSession, SearchProjectPermissionsRequest request) {
199 SearchProjectPermissionsData.Builder data = newBuilder();
200 int countRootComponents = countRootComponents(dbSession, request);
201 List<ComponentDto> rootComponents = searchRootComponents(dbSession, request, paging(request, countRootComponents));
202 List<String> rootComponentUuids = Lists.transform(rootComponents, ComponentDto::uuid);
204 data.rootComponents(rootComponents)
205 .paging(paging(request, countRootComponents))
206 .userCountByProjectIdAndPermission(userCountByRootComponentUuidAndPermission(dbSession, rootComponentUuids))
207 .groupCountByProjectIdAndPermission(groupCountByRootComponentIdAndPermission(dbSession, rootComponentUuids));
212 private static Paging paging(SearchProjectPermissionsRequest request, int total) {
213 return forPageIndex(request.getPage())
214 .withPageSize(request.getPageSize())
218 private int countRootComponents(DbSession dbSession, SearchProjectPermissionsRequest request) {
219 return dbClient.componentDao().countByQuery(dbSession, toDbQuery(request));
222 private List<ComponentDto> searchRootComponents(DbSession dbSession, SearchProjectPermissionsRequest request, Paging paging) {
223 Optional<ProjectWsRef> project = newOptionalWsProjectRef(request.getProjectId(), request.getProjectKey());
225 if (project.isPresent()) {
226 return singletonList(wsSupport.getRootComponentOrModule(dbSession, project.get()));
229 return dbClient.componentDao().selectByQuery(dbSession, toDbQuery(request), paging.offset(), paging.pageSize());
232 private ComponentQuery toDbQuery(SearchProjectPermissionsRequest wsRequest) {
233 return ComponentQuery.builder()
234 .setQualifiers(qualifiers(wsRequest.getQualifier()))
235 .setNameOrKeyQuery(wsRequest.getQuery())
239 private String[] qualifiers(@Nullable String requestQualifier) {
240 return requestQualifier == null
242 : (new String[]{requestQualifier});
245 private Table<String, String, Integer> userCountByRootComponentUuidAndPermission(DbSession dbSession, List<String> rootComponentUuids) {
246 final Table<String, String, Integer> userCountByRootComponentUuidAndPermission = TreeBasedTable.create();
248 dbClient.userPermissionDao().countUsersByProjectPermission(dbSession, rootComponentUuids).forEach(
249 row -> userCountByRootComponentUuidAndPermission.put(row.getComponentUuid(), row.getPermission(), row.getCount()));
251 return userCountByRootComponentUuidAndPermission;
254 private Table<String, String, Integer> groupCountByRootComponentIdAndPermission(DbSession dbSession, List<String> rootComponentUuids) {
255 final Table<String, String, Integer> userCountByRootComponentUuidAndPermission = TreeBasedTable.create();
257 dbClient.groupPermissionDao().groupsCountByComponentUuidAndPermission(dbSession, rootComponentUuids, context -> {
258 CountPerProjectPermission row = context.getResultObject();
259 userCountByRootComponentUuidAndPermission.put(row.getComponentUuid(), row.getPermission(), row.getCount());
262 return userCountByRootComponentUuidAndPermission;
265 private static class SearchProjectPermissionsRequest {
266 private String projectId;
267 private String projectKey;
268 private String qualifier;
269 private Integer page;
270 private Integer pageSize;
271 private String query;
274 public String getProjectId() {
278 public SearchProjectPermissionsRequest setProjectId(@Nullable String projectId) {
279 this.projectId = projectId;
284 public String getProjectKey() {
288 public SearchProjectPermissionsRequest setProjectKey(@Nullable String projectKey) {
289 this.projectKey = projectKey;
294 public Integer getPage() {
298 public SearchProjectPermissionsRequest setPage(int page) {
304 public Integer getPageSize() {
308 public SearchProjectPermissionsRequest setPageSize(int pageSize) {
309 this.pageSize = pageSize;
314 public String getQuery() {
318 public SearchProjectPermissionsRequest setQuery(@Nullable String query) {
324 public String getQualifier() {
328 public SearchProjectPermissionsRequest setQualifier(@Nullable String qualifier) {
329 this.qualifier = qualifier;