]> source.dussan.org Git - sonarqube.git/blob
d9379c4e396674c093b6d3d5e5592c6ed83ff519
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.permission.ws;
21
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;
51
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;
63
64 public class SearchProjectPermissionsAction implements PermissionsWsAction {
65   private static final String PROPERTY_PREFIX = "projects_role.";
66   private static final String DESCRIPTION_SUFFIX = ".desc";
67
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;
75
76   public SearchProjectPermissionsAction(DbClient dbClient, UserSession userSession, I18n i18n, ResourceTypes resourceTypes,
77                                         PermissionWsSupport wsSupport, PermissionService permissionService) {
78     this.dbClient = dbClient;
79     this.userSession = userSession;
80     this.i18n = i18n;
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;
85   }
86
87   @Override
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:" +
92         "<ul>" +
93         "<li>'Administer System'</li>" +
94         "<li>'Administer' rights on the specified project</li>" +
95         "</ul>")
96       .setResponseExample(getClass().getResource("search_project_permissions-example.json"))
97       .setSince("5.2")
98       .setDeprecatedSince("6.5")
99       .addPagingParams(25)
100       .setHandler(this);
101
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>" +
106         "</ul>")
107       .setExampleValue("apac");
108     createProjectParameters(action);
109     createRootQualifierParameter(action, newQualifierParameterContext(i18n, resourceTypes))
110       .setSince("5.3");
111   }
112
113   @Override
114   public void handle(Request wsRequest, Response wsResponse) throws Exception {
115     SearchProjectPermissionsWsResponse searchProjectPermissionsWsResponse = doHandle(toSearchProjectPermissionsWsRequest(wsRequest));
116     writeProtobuf(searchProjectPermissionsWsResponse, wsRequest, wsResponse);
117   }
118
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);
125     }
126   }
127
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));
136   }
137
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);
143     } else {
144       userSession.checkLoggedIn().checkIsSystemAdministrator();
145     }
146   }
147
148   private SearchProjectPermissionsWsResponse buildResponse(SearchProjectPermissionsData data) {
149     SearchProjectPermissionsWsResponse.Builder response = SearchProjectPermissionsWsResponse.newBuilder();
150     Permission.Builder permissionResponse = Permission.newBuilder();
151
152     Project.Builder rootComponentBuilder = Project.newBuilder();
153     for (ComponentDto rootComponent : data.rootComponents()) {
154       rootComponentBuilder
155         .clear()
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(
162           permissionResponse
163             .clear()
164             .setKey(permission)
165             .setUsersCount(data.userCount(rootComponent.uuid(), permission))
166             .setGroupsCount(data.groupCount(rootComponent.uuid(), permission)));
167       }
168       response.addProjects(rootComponentBuilder);
169     }
170
171     for (String permissionKey : permissionService.getAllProjectPermissions()) {
172       response.addPermissions(
173         permissionResponse
174           .clear()
175           .setKey(permissionKey)
176           .setName(i18nName(permissionKey))
177           .setDescription(i18nDescriptionMessage(permissionKey)));
178     }
179
180     Paging paging = data.paging();
181     response.setPaging(
182       Common.Paging.newBuilder()
183         .setPageIndex(paging.pageIndex())
184         .setPageSize(paging.pageSize())
185         .setTotal(paging.total()));
186
187     return response.build();
188   }
189
190   private String i18nDescriptionMessage(String permissionKey) {
191     return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey + DESCRIPTION_SUFFIX, "");
192   }
193
194   private String i18nName(String permissionKey) {
195     return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey, permissionKey);
196   }
197
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);
203
204     data.rootComponents(rootComponents)
205       .paging(paging(request, countRootComponents))
206       .userCountByProjectIdAndPermission(userCountByRootComponentUuidAndPermission(dbSession, rootComponentUuids))
207       .groupCountByProjectIdAndPermission(groupCountByRootComponentIdAndPermission(dbSession, rootComponentUuids));
208
209     return data.build();
210   }
211
212   private static Paging paging(SearchProjectPermissionsRequest request, int total) {
213     return forPageIndex(request.getPage())
214       .withPageSize(request.getPageSize())
215       .andTotal(total);
216   }
217
218   private int countRootComponents(DbSession dbSession, SearchProjectPermissionsRequest request) {
219     return dbClient.componentDao().countByQuery(dbSession, toDbQuery(request));
220   }
221
222   private List<ComponentDto> searchRootComponents(DbSession dbSession, SearchProjectPermissionsRequest request, Paging paging) {
223     Optional<ProjectWsRef> project = newOptionalWsProjectRef(request.getProjectId(), request.getProjectKey());
224
225     if (project.isPresent()) {
226       return singletonList(wsSupport.getRootComponentOrModule(dbSession, project.get()));
227     }
228
229     return dbClient.componentDao().selectByQuery(dbSession, toDbQuery(request), paging.offset(), paging.pageSize());
230   }
231
232   private ComponentQuery toDbQuery(SearchProjectPermissionsRequest wsRequest) {
233     return ComponentQuery.builder()
234       .setQualifiers(qualifiers(wsRequest.getQualifier()))
235       .setNameOrKeyQuery(wsRequest.getQuery())
236       .build();
237   }
238
239   private String[] qualifiers(@Nullable String requestQualifier) {
240     return requestQualifier == null
241       ? rootQualifiers
242       : (new String[]{requestQualifier});
243   }
244
245   private Table<String, String, Integer> userCountByRootComponentUuidAndPermission(DbSession dbSession, List<String> rootComponentUuids) {
246     final Table<String, String, Integer> userCountByRootComponentUuidAndPermission = TreeBasedTable.create();
247
248     dbClient.userPermissionDao().countUsersByProjectPermission(dbSession, rootComponentUuids).forEach(
249       row -> userCountByRootComponentUuidAndPermission.put(row.getComponentUuid(), row.getPermission(), row.getCount()));
250
251     return userCountByRootComponentUuidAndPermission;
252   }
253
254   private Table<String, String, Integer> groupCountByRootComponentIdAndPermission(DbSession dbSession, List<String> rootComponentUuids) {
255     final Table<String, String, Integer> userCountByRootComponentUuidAndPermission = TreeBasedTable.create();
256
257     dbClient.groupPermissionDao().groupsCountByComponentUuidAndPermission(dbSession, rootComponentUuids, context -> {
258       CountPerProjectPermission row = context.getResultObject();
259       userCountByRootComponentUuidAndPermission.put(row.getComponentUuid(), row.getPermission(), row.getCount());
260     });
261
262     return userCountByRootComponentUuidAndPermission;
263   }
264
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;
272
273     @CheckForNull
274     public String getProjectId() {
275       return projectId;
276     }
277
278     public SearchProjectPermissionsRequest setProjectId(@Nullable String projectId) {
279       this.projectId = projectId;
280       return this;
281     }
282
283     @CheckForNull
284     public String getProjectKey() {
285       return projectKey;
286     }
287
288     public SearchProjectPermissionsRequest setProjectKey(@Nullable String projectKey) {
289       this.projectKey = projectKey;
290       return this;
291     }
292
293     @CheckForNull
294     public Integer getPage() {
295       return page;
296     }
297
298     public SearchProjectPermissionsRequest setPage(int page) {
299       this.page = page;
300       return this;
301     }
302
303     @CheckForNull
304     public Integer getPageSize() {
305       return pageSize;
306     }
307
308     public SearchProjectPermissionsRequest setPageSize(int pageSize) {
309       this.pageSize = pageSize;
310       return this;
311     }
312
313     @CheckForNull
314     public String getQuery() {
315       return query;
316     }
317
318     public SearchProjectPermissionsRequest setQuery(@Nullable String query) {
319       this.query = query;
320       return this;
321     }
322
323     @CheckForNull
324     public String getQualifier() {
325       return qualifier;
326     }
327
328     public SearchProjectPermissionsRequest setQualifier(@Nullable String qualifier) {
329       this.qualifier = qualifier;
330       return this;
331     }
332   }
333 }