aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/it/Category1Suite.java2
-rw-r--r--it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java66
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java59
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java52
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java154
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java10
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java60
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java153
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java290
-rw-r--r--server/sonar-web/src/main/js/api/components.js2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java15
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java4
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java25
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java19
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java5
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java113
-rw-r--r--sonar-ws/src/main/protobuf/ws-projects.proto14
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java23
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java60
26 files changed, 1128 insertions, 53 deletions
diff --git a/it/it-tests/src/test/java/it/Category1Suite.java b/it/it-tests/src/test/java/it/Category1Suite.java
index 067db2094d2..af6812ff25a 100644
--- a/it/it-tests/src/test/java/it/Category1Suite.java
+++ b/it/it-tests/src/test/java/it/Category1Suite.java
@@ -20,6 +20,7 @@
package it;
import com.sonar.orchestrator.Orchestrator;
+import it.administration.ProjectsAdministrationTest;
import it.administration.UsersPageTest;
import it.authorisation.ExecuteAnalysisPermissionTest;
import it.authorisation.IssuePermissionTest;
@@ -62,6 +63,7 @@ import static util.ItUtils.xooPlugin;
@Suite.SuiteClasses({
// administration
UsersPageTest.class,
+ ProjectsAdministrationTest.class,
// project administration
BulkDeletionTest.class,
ProjectAdministrationTest.class,
diff --git a/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java b/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java
new file mode 100644
index 00000000000..c6258e7b886
--- /dev/null
+++ b/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package it.administration;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import it.Category1Suite;
+import java.sql.SQLException;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.ws.client.permission.RemoveGroupWsRequest;
+import pageobjects.Navigation;
+
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selenide.$;
+import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.projectDir;
+
+public class ProjectsAdministrationTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+
+ @Rule
+ public Navigation nav = Navigation.get(orchestrator);
+
+ @Before
+ public void deleteAnalysisData() throws SQLException {
+ orchestrator.resetData();
+ }
+
+ @Test
+ public void return_all_projects_even_when_no_permission() throws Exception {
+ orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample1"));
+ orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample2"));
+ // Remove 'Browse' permission from anyone and 'Admin' permission for admin group on project 2 -> No one can access or admin this
+ // project, expect System Admin
+ newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("Anyone").setPermission("user"));
+ newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("sonar-administrators").setPermission("admin"));
+
+ nav.logIn().asAdmin().open("/projects_admin");
+ $(".data.zebra")
+ .shouldHave(text("sample1"))
+ .shouldHave(text("sample2"));
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java
index 4f542a461e3..72ed9b83392 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java
@@ -23,6 +23,7 @@ import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.UserDto;
@@ -32,6 +33,7 @@ import static java.util.Objects.requireNonNull;
public interface OrganizationCreation {
String OWNERS_GROUP_NAME = "Owners";
String OWNERS_GROUP_DESCRIPTION_PATTERN = "Owners of organization %s";
+ String PERM_TEMPLATE_NAME = "Default template";
String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s";
String PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN = "%s's personal organization";
@@ -42,16 +44,21 @@ public interface OrganizationCreation {
* This method does several operations at once:
* <ol>
* <li>create an ungarded organization with the specified details</li>
- * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with Administer Organization permission</li>
+ * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with all organization wide permissions</li>
* <li>make the specified user a member of this group</li>
- * <li>create a default template for the organization (which name and description will follow patterns
- * {@link #OWNERS_GROUP_NAME} and {@link #OWNERS_GROUP_DESCRIPTION_PATTERN} based on the organization name)</li>
- * <li>this group defines the specified permissions (which effectively makes projects public):
+ * <li>create a default template for the organization
+ * <ul>
+ * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
+ * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
+ * </ul>
+ * </li>
+ * <li>this permission template defines the specified permissions (which effectively makes projects public):
* <ul>
* <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ADMIN ADMIN}</li>
* <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
- * <li>any one : {@link UserRole#USER USER}</li>
- * <li>any one : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
+ * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
+ * <li>anyone : {@link UserRole#USER USER}</li>
+ * <li>anyone : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
* </ul>
* </li>
* </ol>
@@ -67,16 +74,36 @@ public interface OrganizationCreation {
/**
* Create a new guarded organization which details are based on the login of the specified User.
* <p>
- * This method create the organization and its associated elements in exactly the same was as
- * {@link #create(DbSession, long, NewOrganization)} with the organization's details computed from the
- * user's login:
- * <ul>
- * <li>key: generated from the user's login</li>
- * <li>name: the user's name if set, otherwise the user's login</li>
- * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name
- * is user name (when non null and non empty) or login</li>
- * <li>url and avatar: null</li>
- * </ul>
+ * This method does several operations at once:
+ * <ol>
+ * <li>
+ * create a guarded organization with the details computed from user's details:
+ * <ul>
+ * <li>key: generated from the user's login</li>
+ * <li>name: the user's name if set, otherwise the user's login</li>
+ * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name
+ * is user name (when non null and non empty) or login</li>
+ * <li>url and avatar: null</li>
+ * </ul>
+ * </li>
+ * <li>give all organization wide permissions to the user</li>
+ * <li>create a default template for the organization
+ * <ul>
+ * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
+ * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
+ * </ul>
+ * </li>
+ * <li>this permission template defines the specified permissions (which effectively makes projects public and
+ * automatically adds new projects to the user's favorites):
+ * <ul>
+ * <li>project creator : {@link UserRole#ADMIN ADMIN}</li>
+ * <li>project creator : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
+ * <li>project creator : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
+ * <li>anyone : {@link UserRole#USER USER}</li>
+ * <li>anyone : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
+ * </ul>
+ * </li>
+ * </ol>
* </p>
*
* @return the created organization or empty if feature is disabled
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java
index e342fa7ea65..81de1e7cc40 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java
@@ -34,6 +34,8 @@ import org.sonar.db.DbSession;
import org.sonar.db.organization.DefaultTemplates;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.permission.GroupPermissionDto;
+import org.sonar.db.permission.UserPermissionDto;
+import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
import org.sonar.db.permission.template.PermissionTemplateDto;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
@@ -99,9 +101,8 @@ public class OrganizationCreationImpl implements OrganizationCreation {
OrganizationDto organization = insertOrganization(dbSession, newOrganization,
dto -> dto.setGuarded(true).setUserId(newUser.getId()));
- GroupDto group = insertOwnersGroup(dbSession, organization);
- insertDefaultTemplate(dbSession, organization, group);
- addCurrentUserToGroup(dbSession, group, newUser.getId());
+ GlobalPermissions.ALL.forEach(permission -> insertUserPermissions(dbSession, newUser, organization, permission));
+ insertPersonalOrgDefaultTemplate(dbSession, organization);
dbSession.commit();
@@ -160,13 +161,39 @@ public class OrganizationCreationImpl implements OrganizationCreation {
new PermissionTemplateDto()
.setOrganizationUuid(organizationDto.getUuid())
.setUuid(uuidFactory.create())
- .setName("Default template")
+ .setName(PERM_TEMPLATE_NAME)
.setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
.setCreatedAt(now)
.setUpdatedAt(now));
insertGroupPermission(dbSession, permissionTemplateDto, UserRole.ADMIN, group);
insertGroupPermission(dbSession, permissionTemplateDto, UserRole.ISSUE_ADMIN, group);
+ insertGroupPermission(dbSession, permissionTemplateDto, GlobalPermissions.SCAN_EXECUTION, group);
+ insertGroupPermission(dbSession, permissionTemplateDto, UserRole.USER, null);
+ insertGroupPermission(dbSession, permissionTemplateDto, UserRole.CODEVIEWER, null);
+
+ dbClient.organizationDao().setDefaultTemplates(
+ dbSession,
+ organizationDto.getUuid(),
+ new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
+ }
+
+ private void insertPersonalOrgDefaultTemplate(DbSession dbSession, OrganizationDto organizationDto) {
+ long now = system2.now();
+ Date dateNow = new Date(now);
+ PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert(
+ dbSession,
+ new PermissionTemplateDto()
+ .setOrganizationUuid(organizationDto.getUuid())
+ .setUuid(uuidFactory.create())
+ .setName("Default template")
+ .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
+ .setCreatedAt(dateNow)
+ .setUpdatedAt(dateNow));
+
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, UserRole.ADMIN, now);
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, UserRole.ISSUE_ADMIN, now);
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, GlobalPermissions.SCAN_EXECUTION, now);
insertGroupPermission(dbSession, permissionTemplateDto, UserRole.USER, null);
insertGroupPermission(dbSession, permissionTemplateDto, UserRole.CODEVIEWER, null);
@@ -176,6 +203,17 @@ public class OrganizationCreationImpl implements OrganizationCreation {
new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
}
+ private void insertProjectCreatorPermission(DbSession dbSession, PermissionTemplateDto permissionTemplateDto, String permission, long now) {
+ dbClient.permissionTemplateCharacteristicDao().insert(
+ dbSession,
+ new PermissionTemplateCharacteristicDto()
+ .setTemplateId(permissionTemplateDto.getId())
+ .setWithProjectCreator(true)
+ .setPermission(permission)
+ .setCreatedAt(now)
+ .setUpdatedAt(now));
+ }
+
private void insertGroupPermission(DbSession dbSession, PermissionTemplateDto template, String permission, @Nullable GroupDto group) {
dbClient.permissionTemplateDao().insertGroupPermission(dbSession, template.getId(), group == null ? null : group.getId(), permission);
}
@@ -201,6 +239,12 @@ public class OrganizationCreationImpl implements OrganizationCreation {
.setRole(permission));
}
+ private void insertUserPermissions(DbSession dbSession, UserDto userDto, OrganizationDto organization, String permission) {
+ dbClient.userPermissionDao().insert(
+ dbSession,
+ new UserPermissionDto(organization.getUuid(), permission, userDto.getId(), null));
+ }
+
private void addCurrentUserToGroup(DbSession dbSession, GroupDto group, int createUserId) {
dbClient.userGroupDao().insert(
dbSession,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java
index 3ca3359473c..6d4c1c33b6b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java
@@ -85,6 +85,10 @@ public class AuthorizationTypeSupport {
* user has read access.
*/
public QueryBuilder createQueryFilter() {
+ if (userSession.isRoot()) {
+ return QueryBuilders.matchAllQuery();
+ }
+
Integer userId = userSession.getUserId();
BoolQueryBuilder filter = boolQuery();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
index ee421e8213c..d3c00cf99ed 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
@@ -34,6 +34,7 @@ public class ProjectsWsModule extends Module {
GhostsAction.class,
ProvisionedAction.class,
SearchMyProjectsAction.class,
- SearchMyProjectsDataLoader.class);
+ SearchMyProjectsDataLoader.class,
+ SearchAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
new file mode 100644
index 00000000000..3ca4f103486
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
@@ -0,0 +1,154 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.project.ws;
+
+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.utils.Paging;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentQuery;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.WsProjects.SearchWsResponse;
+import org.sonarqube.ws.client.project.SearchWsRequest;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Optional.ofNullable;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.api.resources.Qualifiers.VIEW;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.WsProjects.SearchWsResponse.Component;
+import static org.sonarqube.ws.WsProjects.SearchWsResponse.newBuilder;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
+
+public class SearchAction implements ProjectsWsAction {
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final DefaultOrganizationProvider defaultOrganizationProvider;
+ private final ProjectsWsSupport support;
+
+ public SearchAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, ProjectsWsSupport support) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.defaultOrganizationProvider = defaultOrganizationProvider;
+ this.support = support;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction(ACTION_SEARCH)
+ .setSince("6.3")
+ .setDescription("Search for projects or views.<br>" +
+ "Requires 'System Administrator' permission")
+ .setInternal(true)
+ .addPagingParams(100, MAX_PAGE_SIZE)
+ .addSearchQuery("sona", "component names", "component keys")
+ .setResponseExample(getClass().getResource("search-example.json"))
+ .setHandler(this);
+ action.createParam(PARAM_QUALIFIERS)
+ .setDescription("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers")
+ .setPossibleValues(PROJECT, VIEW)
+ .setDefaultValue(PROJECT);
+ support.addOrganizationParam(action);
+ }
+
+ @Override
+ public void handle(Request wsRequest, Response wsResponse) throws Exception {
+ SearchWsResponse searchWsResponse = doHandle(toSearchWsRequest(wsRequest));
+ writeProtobuf(searchWsResponse, wsRequest, wsResponse);
+ }
+
+ private static SearchWsRequest toSearchWsRequest(Request request) {
+ return SearchWsRequest.builder()
+ .setOrganization(request.param(PARAM_ORGANIZATION))
+ .setQualifiers(request.mandatoryParamAsStrings(PARAM_QUALIFIERS))
+ .setQuery(request.param(Param.TEXT_QUERY))
+ .setPage(request.mandatoryParamAsInt(Param.PAGE))
+ .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)).build();
+ }
+
+ private SearchWsResponse doHandle(SearchWsRequest request) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ OrganizationDto organization = support.getOrganization(dbSession, ofNullable(request.getOrganization()).orElseGet(defaultOrganizationProvider.get()::getKey));
+ userSession.checkOrganizationPermission(organization.getUuid(), SYSTEM_ADMIN);
+
+ ComponentQuery query = buildQuery(request);
+ Paging paging = buildPaging(dbSession, request, organization, query);
+ List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, paging.offset(), paging.pageSize());
+ return buildResponse(components, organization, paging);
+ }
+ }
+
+ private static ComponentQuery buildQuery(SearchWsRequest request) {
+ List<String> qualifiers = request.getQualifiers();
+ return ComponentQuery.builder()
+ .setNameOrKeyQuery(request.getQuery())
+ .setQualifiers(qualifiers.toArray(new String[qualifiers.size()]))
+ .build();
+ }
+
+ private Paging buildPaging(DbSession dbSession, SearchWsRequest request, OrganizationDto organization, ComponentQuery query) {
+ int total = dbClient.componentDao().countByQuery(dbSession, organization.getUuid(), query);
+ return Paging.forPageIndex(request.getPage())
+ .withPageSize(request.getPageSize())
+ .andTotal(total);
+ }
+
+ private static SearchWsResponse buildResponse(List<ComponentDto> components, OrganizationDto organization, Paging paging) {
+ SearchWsResponse.Builder responseBuilder = newBuilder();
+ responseBuilder.getPagingBuilder()
+ .setPageIndex(paging.pageIndex())
+ .setPageSize(paging.pageSize())
+ .setTotal(paging.total())
+ .build();
+
+ components.stream()
+ .map(dto -> dtoToProject(organization, dto))
+ .forEach(responseBuilder::addComponents);
+ return responseBuilder.build();
+ }
+
+ private static Component dtoToProject(OrganizationDto organization, ComponentDto dto) {
+ checkArgument(
+ organization.getUuid().equals(dto.getOrganizationUuid()),
+ "No Organization found for uuid '%s'",
+ dto.getOrganizationUuid());
+
+ Component.Builder builder = Component.newBuilder()
+ .setOrganization(organization.getKey())
+ .setId(dto.uuid())
+ .setKey(dto.key())
+ .setName(dto.name())
+ .setQualifier(dto.qualifier());
+ return builder.build();
+ }
+
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json
new file mode 100644
index 00000000000..fca854523c0
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json
@@ -0,0 +1,23 @@
+{
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 2
+ },
+ "components": [
+ {
+ "organization": "my-org-1",
+ "id": "project-uuid-1",
+ "key": "project-key-1",
+ "name": "Project Name 1",
+ "qualifier": "TRK"
+ },
+ {
+ "organization": "my-org-1",
+ "id": "project-uuid-2",
+ "key": "project-key-2",
+ "name": "Project Name 1",
+ "qualifier": "TRK"
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java
index b294e4ea4e5..c5b30d5db51 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java
@@ -68,4 +68,14 @@ public class ComponentIndexLoginTest extends ComponentIndexTest {
authorizationIndexerTester.allowOnlyGroup(project, group);
assertSearchResults("sonarqube", project);
}
+
+ @Test
+ public void do_not_check_permissions_when_logged_in_user_is_root() {
+ userSession.logIn().setRoot();
+ ComponentDto project = newProject("sonarqube", "Quality Product");
+ indexer.index(project);
+ // do not give any permissions to that project
+
+ assertSearchResults("sonarqube", project);
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
index a27cf1661be..a0b31e5f8e5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
@@ -1218,6 +1218,15 @@ public class IssueIndexTest {
}
@Test
+ public void root_user_is_authorized_to_access_all_issues() {
+ ComponentDto project = newProjectDto(newOrganizationDto());
+ indexIssue(IssueDocTesting.newDoc("I1", project));
+ userSessionRule.logIn().setRoot();
+
+ assertThat(underTest.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1);
+ }
+
+ @Test
public void search_issues_for_batch_return_needed_fields() {
ComponentDto project = newProjectDto(newOrganizationDto(), "PROJECT");
ComponentDto file = newFileDto(project, null).setPath("src/File.xoo");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
index c6ba98edeb4..435ae83e8b8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
@@ -313,6 +313,15 @@ public class ProjectMeasuresIndexTest {
}
@Test
+ public void root_user_can_access_all_projects() {
+ indexForUser(USER1, newDoc(PROJECT1));
+ // connecting with a root but not USER1
+ userSession.logIn().setRoot();
+
+ assertResults(new ProjectMeasuresQuery(), PROJECT1);
+ }
+
+ @Test
public void does_not_return_facet_when_no_facets_in_options() throws Exception {
index(
newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java
index 3ca80415832..fa34f3ae17c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java
@@ -19,8 +19,8 @@
*/
package org.sonar.server.organization;
+import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -35,6 +35,7 @@ import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.organization.DefaultTemplates;
import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
import org.sonar.db.permission.template.PermissionTemplateDto;
import org.sonar.db.permission.template.PermissionTemplateGroupDto;
import org.sonar.db.user.GroupDto;
@@ -57,6 +58,7 @@ public class OrganizationCreationImplTest {
private static final String SLUG_OF_A_LOGIN = "slug-of-a-login";
private static final String STRING_64_CHARS = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
private static final String A_NAME = "a name";
+ private static final int ANYONE_GROUP_ID = 0;
private OrganizationCreation.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder()
.setName("a-name")
@@ -194,7 +196,19 @@ public class OrganizationCreationImplTest {
underTest.create(dbSession, SOME_USER_ID, FULL_POPULATED_NEW_ORGANIZATION);
- verifyDefaultTemplate(FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName());
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
+ GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get();
+ PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
+ assertThat(defaultTemplate.getName()).isEqualTo("Default template");
+ assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + FULL_POPULATED_NEW_ORGANIZATION.getName());
+ DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
+ assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
+ assertThat(defaultTemplates.getViewUuid()).isNull();
+ assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
+ .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
+ .containsOnly(
+ tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION),
+ tuple(ANYONE_GROUP_ID, UserRole.USER), tuple(ANYONE_GROUP_ID, UserRole.CODEVIEWER));
}
@Test
@@ -264,7 +278,21 @@ public class OrganizationCreationImplTest {
}
@Test
- public void createForUser_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException {
+ public void createForUser_gives_all_permissions_for_new_organization_to_current_user() throws OrganizationCreation.KeyConflictException {
+ UserDto user = dbTester.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ mockForSuccessfulInsert(SOME_UUID, SOME_DATE);
+ enableCreatePersonalOrg(true);
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), organization.getUuid()))
+ .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
+ }
+
+ @Test
+ public void createForUser_does_not_create_any_group() throws OrganizationCreation.KeyConflictException {
UserDto user = dbTester.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
mockForSuccessfulInsert(SOME_UUID, SOME_DATE);
@@ -272,14 +300,17 @@ public class OrganizationCreationImplTest {
underTest.createForUser(dbSession, user);
- verifyGroupOwners(user, SLUG_OF_A_LOGIN, A_NAME);
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(dbClient.groupDao().selectByOrganizationUuid(dbSession, organization.getUuid())).isEmpty();
}
private void verifyGroupOwners(UserDto user, String organizationKey, String organizationName) {
OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
- Optional<GroupDto> groupDtoOptional = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners");
- assertThat(groupDtoOptional).isNotEmpty();
- GroupDto groupDto = groupDtoOptional.get();
+ List<GroupDto> groups = dbClient.groupDao().selectByOrganizationUuid(dbSession, organization.getUuid());
+ assertThat(groups)
+ .extracting(GroupDto::getName)
+ .containsOnly("Owners");
+ GroupDto groupDto = groups.iterator().next();
assertThat(groupDto.getDescription()).isEqualTo("Owners of organization " + organizationName);
assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId()))
.containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
@@ -300,23 +331,20 @@ public class OrganizationCreationImplTest {
underTest.createForUser(dbSession, user);
- verifyDefaultTemplate(SLUG_OF_A_LOGIN, A_NAME);
- }
-
- private void verifyDefaultTemplate(String organizationKey, String organizationName) {
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
- GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get();
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
assertThat(defaultTemplate.getName()).isEqualTo("Default template");
- assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + organizationName);
+ assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + A_NAME);
DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
assertThat(defaultTemplates.getViewUuid()).isNull();
assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
.extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
+ .containsOnly(tuple(ANYONE_GROUP_ID, UserRole.USER), tuple(ANYONE_GROUP_ID, UserRole.CODEVIEWER));
+ assertThat(dbClient.permissionTemplateCharacteristicDao().selectByTemplateIds(dbSession, Collections.singletonList(defaultTemplate.getId())))
+ .extracting(PermissionTemplateCharacteristicDto::getWithProjectCreator, PermissionTemplateCharacteristicDto::getPermission)
.containsOnly(
- tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN),
- tuple(0, UserRole.USER), tuple(0, UserRole.CODEVIEWER));
+ tuple(true, UserRole.ADMIN), tuple(true, UserRole.ISSUE_ADMIN), tuple(true, GlobalPermissions.SCAN_EXECUTION));
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
index 50ba6976f19..f56fa73f83d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
@@ -91,7 +91,8 @@ public class CreateActionTest {
private OrganizationValidation organizationValidation = new OrganizationValidationImpl();
private OrganizationCreation organizationCreation = new OrganizationCreationImpl(dbClient, system2, uuidFactory, organizationValidation, settings);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true);
- private CreateAction underTest = new CreateAction(settings, userSession, dbClient, new OrganizationsWsSupport(organizationValidation), organizationValidation, organizationCreation, organizationFlags);
+ private CreateAction underTest = new CreateAction(settings, userSession, dbClient, new OrganizationsWsSupport(organizationValidation), organizationValidation,
+ organizationCreation, organizationFlags);
private WsActionTester wsTester = new WsActionTester(underTest);
@Test
@@ -486,7 +487,7 @@ public class CreateActionTest {
assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
.extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
.containsOnly(
- tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN),
+ tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION),
tuple(0, UserRole.USER), tuple(0, UserRole.CODEVIEWER));
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java
new file mode 100644
index 00000000000..6b1423222c2
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.permission.index;
+
+import org.elasticsearch.index.query.HasParentQueryBuilder;
+import org.elasticsearch.index.query.MatchAllQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.GroupTesting;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class AuthorizationTypeSupportTest {
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ private AuthorizationTypeSupport underTest = new AuthorizationTypeSupport(userSession);
+
+ @Test
+ public void createQueryFilter_does_not_include_permission_filters_if_user_is_flagged_as_root() {
+ userSession.logIn().setRoot();
+
+ QueryBuilder filter = underTest.createQueryFilter();
+
+ assertThat(filter).isInstanceOf(MatchAllQueryBuilder.class);
+ }
+
+ @Test
+ public void createQueryFilter_sets_filter_on_anyone_group_if_user_is_anonymous() {
+ userSession.anonymous();
+
+ HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+ assertJson(filter.toString()).isSimilarTo("{" +
+ " \"has_parent\" : {" +
+ " \"query\" : {" +
+ " \"bool\" : {" +
+ " \"filter\" : {" +
+ " \"bool\" : {" +
+ " \"should\" : {" +
+ " \"term\" : {" +
+ " \"allowAnyone\" : true" +
+ " }" +
+ " }" +
+ " }" +
+ " }" +
+ " }" +
+ " }," +
+ " \"parent_type\" : \"authorization\"" +
+ " }" +
+ "}");
+ }
+
+ @Test
+ public void createQueryFilter_sets_filter_on_anyone_and_user_id_if_user_is_logged_in_but_has_no_groups() {
+ userSession.logIn().setUserId(1234);
+
+ HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+ assertJson(filter.toString()).isSimilarTo("{" +
+ " \"has_parent\": {" +
+ " \"query\": {" +
+ " \"bool\": {" +
+ " \"filter\": {" +
+ " \"bool\": {" +
+ " \"should\": [" +
+ " {" +
+ " \"term\": {" +
+ " \"allowAnyone\": true" +
+ " }" +
+ " }," +
+ " {" +
+ " \"term\": {" +
+ " \"userIds\": 1234" +
+ " }" +
+ " }" +
+ " ]" +
+ " }" +
+ " }" +
+ " }" +
+ " }," +
+ " \"parent_type\": \"authorization\"" +
+ " }" +
+ "}");
+ }
+
+ @Test
+ public void createQueryFilter_sets_filter_on_anyone_and_user_id_and_group_ids_if_user_is_logged_in_and_has_groups() {
+ GroupDto group1 = GroupTesting.newGroupDto().setId(10);
+ GroupDto group2 = GroupTesting.newGroupDto().setId(11);
+ userSession.logIn().setUserId(1234).setGroups(group1, group2);
+
+ HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter();
+
+ assertJson(filter.toString()).isSimilarTo("{" +
+ " \"has_parent\": {" +
+ " \"query\": {" +
+ " \"bool\": {" +
+ " \"filter\": {" +
+ " \"bool\": {" +
+ " \"should\": [" +
+ " {" +
+ " \"term\": {" +
+ " \"allowAnyone\": true" +
+ " }" +
+ " }," +
+ " {" +
+ " \"term\": {" +
+ " \"userIds\": 1234" +
+ " }" +
+ " }," +
+ " {" +
+ " \"term\": {" +
+ " \"groupIds\": 10" +
+ " }" +
+ " }," +
+ " {" +
+ " \"term\": {" +
+ " \"groupIds\": 11" +
+ " }" +
+ " }" +
+ " ]" +
+ " }" +
+ " }" +
+ " }" +
+ " }," +
+ " \"parent_type\": \"authorization\"" +
+ " }" +
+ "}");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
index e6c03e41ebf..abd0abd5105 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
@@ -30,6 +30,6 @@ public class ProjectsWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new ProjectsWsModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 10);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 11);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
new file mode 100644
index 00000000000..1aa03aa609f
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
@@ -0,0 +1,290 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.project.ws;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsProjects.SearchWsResponse;
+import org.sonarqube.ws.WsProjects.SearchWsResponse.Component;
+import org.sonarqube.ws.client.component.ComponentsWsParameters;
+import org.sonarqube.ws.client.project.SearchWsRequest;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
+import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.core.permission.GlobalPermissions.QUALITY_PROFILE_ADMIN;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.ComponentTesting.newView;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.MediaTypes.PROTOBUF;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
+
+public class SearchActionTest {
+
+ private static final String PROJECT_KEY_1 = "project1";
+ private static final String PROJECT_KEY_2 = "project2";
+ private static final String PROJECT_KEY_3 = "project3";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+
+ private WsActionTester ws = new WsActionTester(new SearchAction(db.getDbClient(), userSession, defaultOrganizationProvider, new ProjectsWsSupport(db.getDbClient())));
+
+ @Test
+ public void search_by_key_query() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ db.components().insertComponents(
+ newProjectDto(db.getDefaultOrganization()).setKey("project-_%-key"),
+ newProjectDto(db.getDefaultOrganization()).setKey("project-key-without-escaped-characters"));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setQuery("project-_%-key").build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key");
+ }
+
+ @Test
+ public void search_projects_when_no_qualifier_set() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ db.components().insertComponents(
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1),
+ newView(db.getDefaultOrganization()));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1);
+ }
+
+ @Test
+ public void search_projects() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ ComponentDto project = newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1);
+ ComponentDto module = newModuleDto(project);
+ ComponentDto directory = newDirectory(module, "dir");
+ ComponentDto file = newFileDto(directory);
+ db.components().insertComponents(
+ project, module, directory, file,
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_2),
+ newView(db.getDefaultOrganization()));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(singletonList("TRK")).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, PROJECT_KEY_2);
+ }
+
+ @Test
+ public void search_views() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ db.components().insertComponents(
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1),
+ newView(db.getDefaultOrganization()).setKey("view1"));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(singletonList("VW")).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("view1");
+ }
+
+ @Test
+ public void search_projects_and_views() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ db.components().insertComponents(
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1),
+ newView(db.getDefaultOrganization()).setKey("view1"));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(asList("TRK", "VW")).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, "view1");
+ }
+
+ @Test
+ public void search_on_default_organization_when_no_organization_set() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ OrganizationDto otherOrganization = db.organizations().insert();
+ db.components().insertComponents(
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1),
+ newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_2),
+ newProjectDto(otherOrganization).setKey(PROJECT_KEY_3));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, PROJECT_KEY_2);
+ }
+
+ @Test
+ public void search_for_projects_on_given_organization() throws IOException {
+ OrganizationDto organization1 = db.organizations().insert();
+ OrganizationDto organization2 = db.organizations().insert();
+ userSession.addOrganizationPermission(organization1, SYSTEM_ADMIN);
+ ComponentDto project1 = newProjectDto(organization1);
+ ComponentDto project2 = newProjectDto(organization1);
+ ComponentDto project3 = newProjectDto(organization2);
+ db.components().insertComponents(project1, project2, project3);
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setOrganization(organization1.getKey()).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(project1.key(), project2.key());
+ }
+
+ @Test
+ public void result_is_paginated() throws IOException {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN);
+ List<ComponentDto> componentDtoList = new ArrayList<>();
+ for (int i = 1; i <= 9; i++) {
+ componentDtoList.add(newProjectDto(db.getDefaultOrganization(), "project-uuid-" + i).setKey("project-key-" + i).setName("Project Name " + i));
+ }
+ db.components().insertComponents(componentDtoList.toArray(new ComponentDto[] {}));
+
+ SearchWsResponse response = call(SearchWsRequest.builder().setPage(2).setPageSize(3).build());
+
+ assertThat(response.getComponentsList()).extracting(Component::getKey).containsExactly("project-key-4", "project-key-5", "project-key-6");
+ }
+
+ @Test
+ public void fail_when_not_system_admin() throws Exception {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), QUALITY_PROFILE_ADMIN);
+ expectedException.expect(ForbiddenException.class);
+
+ call(SearchWsRequest.builder().build());
+ }
+
+ @Test
+ public void fail_on_unknown_organization() throws Exception {
+ expectedException.expect(NotFoundException.class);
+
+ call(SearchWsRequest.builder().setOrganization("unknown").build());
+ }
+
+ @Test
+ public void fail_on_invalid_qualifier() throws Exception {
+ userSession.addOrganizationPermission(db.getDefaultOrganization(), QUALITY_PROFILE_ADMIN);
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of parameter 'qualifiers' (BRC) must be one of: [TRK, VW]");
+
+ call(SearchWsRequest.builder().setQualifiers(singletonList("BRC")).build());
+ }
+
+ @Test
+ public void verify_define() {
+ WebService.Action action = ws.getDef();
+ assertThat(action.key()).isEqualTo("search");
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.description()).isEqualTo("Search for projects or views.<br>Requires 'System Administrator' permission");
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.since()).isEqualTo("6.3");
+ assertThat(action.handler()).isEqualTo(ws.getDef().handler());
+ assertThat(action.params()).hasSize(5);
+ assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json"));
+
+ WebService.Param organization = action.param("organization");
+ Assertions.assertThat(organization.description()).isEqualTo("The key of the organization");
+ Assertions.assertThat(organization.isInternal()).isTrue();
+ Assertions.assertThat(organization.isRequired()).isFalse();
+ Assertions.assertThat(organization.since()).isEqualTo("6.3");
+
+ WebService.Param qParam = action.param("q");
+ assertThat(qParam.isRequired()).isFalse();
+ assertThat(qParam.description()).isEqualTo("Limit search to component names or component keys that contain the supplied string.");
+
+ WebService.Param qualifierParam = action.param("qualifiers");
+ assertThat(qualifierParam.isRequired()).isFalse();
+ assertThat(qualifierParam.description()).isEqualTo("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers");
+ assertThat(qualifierParam.possibleValues()).containsOnly("TRK", "VW");
+ assertThat(qualifierParam.defaultValue()).isEqualTo("TRK");
+
+ WebService.Param pParam = action.param("p");
+ assertThat(pParam.isRequired()).isFalse();
+ assertThat(pParam.defaultValue()).isEqualTo("1");
+ assertThat(pParam.description()).isEqualTo("1-based page number");
+
+ WebService.Param psParam = action.param("ps");
+ assertThat(psParam.isRequired()).isFalse();
+ assertThat(psParam.defaultValue()).isEqualTo("100");
+ assertThat(psParam.description()).isEqualTo("Page size. Must be greater than 0 and less than 500");
+ }
+
+ @Test
+ public void verify_response_example() throws URISyntaxException, IOException {
+ OrganizationDto organizationDto = db.organizations().insertForKey("my-org-1");
+ userSession.addOrganizationPermission(organizationDto, SYSTEM_ADMIN);
+ db.components().insertComponents(
+ newProjectDto(organizationDto, "project-uuid-1").setName("Project Name 1").setKey("project-key-1"),
+ newProjectDto(organizationDto, "project-uuid-2").setName("Project Name 1").setKey("project-key-2"));
+
+ String response = ws.newRequest()
+ .setMediaType(MediaTypes.JSON)
+ .setParam(PARAM_ORGANIZATION, organizationDto.getKey())
+ .execute().getInput();
+ assertJson(response).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ private SearchWsResponse call(SearchWsRequest wsRequest) {
+ TestRequest request = ws.newRequest()
+ .setMediaType(PROTOBUF);
+ setNullable(wsRequest.getOrganization(), organization -> request.setParam(PARAM_ORGANIZATION, organization));
+ List<String> qualifiers = wsRequest.getQualifiers();
+ if (!qualifiers.isEmpty()) {
+ request.setParam(ComponentsWsParameters.PARAM_QUALIFIERS, Joiner.on(",").join(qualifiers));
+ }
+ setNullable(wsRequest.getQuery(), query -> request.setParam(TEXT_QUERY, query));
+ setNullable(wsRequest.getPage(), page -> request.setParam(PAGE, String.valueOf(page)));
+ setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(PAGE_SIZE, String.valueOf(pageSize)));
+ try {
+ return SearchWsResponse.parseFrom(request.execute().getInputStream());
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+}
diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js
index e47820b33f7..53007d59a45 100644
--- a/server/sonar-web/src/main/js/api/components.js
+++ b/server/sonar-web/src/main/js/api/components.js
@@ -21,7 +21,7 @@
import { getJSON, postJSON, post } from '../helpers/request';
export function getComponents (data?: Object) {
- const url = '/api/components/search';
+ const url = '/api/projects/search';
return getJSON(url, data);
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java
index 4cc2da8bb21..2f650bffe61 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java
@@ -19,6 +19,8 @@
*/
package org.sonar.api.utils;
+import static com.google.common.base.Preconditions.checkArgument;
+
/**
* @since 3.6
*/
@@ -29,16 +31,9 @@ public class Paging {
private final int total;
private Paging(int pageSize, int pageIndex, int total) {
- if (pageSize < 1) {
- throw new IllegalArgumentException("Page size must be strictly positive. Got " + pageSize);
- }
- if (pageIndex < 1) {
- throw new IllegalArgumentException("Page index must be strictly positive. Got " + pageIndex);
- }
- if (total < 0) {
- throw new IllegalArgumentException("Total items must be positive. Got " + total);
- }
-
+ checkArgument(pageSize >= 1, "Page size must be strictly positive. Got %s", pageSize);
+ checkArgument(pageIndex >= 1, "Page index must be strictly positive. Got %s", pageIndex);
+ checkArgument(total >= 0, "Total items must be positive. Got %s", total);
this.pageSize = pageSize;
this.pageIndex = pageIndex;
this.total = total;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
index 7bbb1247828..332a976d76c 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
@@ -238,6 +238,10 @@ public class ProjectScanContainer extends ComponentContainer {
if (StringUtils.isNotEmpty(organization)) {
LOG.info("Organization key: {}", organization);
}
+ String branch = tree.root().definition().getBranch();
+ if (branch != null) {
+ LOG.info("Branch key: {}", branch);
+ }
LOG.debug("Start recursive analysis of project modules");
scanRecursively(tree, tree.root());
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
index 64a3018c6a3..96e8df3f64d 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
@@ -135,6 +135,7 @@ public class FileSystemMediumTest {
public void logProjectKeyAndOrganizationKey() throws IOException {
builder = createBuilder();
builder.put("sonar.organization", "my org");
+ builder.put("sonar.branch", "");
File srcDir = new File(baseDir, "src");
srcDir.mkdir();
@@ -149,8 +150,29 @@ public class FileSystemMediumTest {
assertThat(logs.getAllAsString()).contains("Project key: com.foo.project");
assertThat(logs.getAllAsString()).contains("Organization key: my org");
+ assertThat(logs.getAllAsString()).doesNotContain("Branch key");
}
-
+
+ @Test
+ public void logBranchKey() throws IOException {
+ builder = createBuilder();
+ builder.put("sonar.branch", "my-branch");
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\ncontent");
+
+ tester.newTask()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .start();
+
+ assertThat(logs.getAllAsString()).contains("Project key: com.foo.project");
+ assertThat(logs.getAllAsString()).contains("Branch key: my-branch");
+ }
+
@Test
public void dontLogInvalidOrganization() throws IOException {
builder = createBuilder();
@@ -168,6 +190,7 @@ public class FileSystemMediumTest {
assertThat(logs.getAllAsString()).contains("Project key: com.foo.project");
assertThat(logs.getAllAsString()).doesNotContain("Organization key");
+ assertThat(logs.getAllAsString()).doesNotContain("Branch key");
}
@Test
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java
index e3142f248bd..b2646fa7ccc 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java
@@ -19,16 +19,23 @@
*/
package org.sonarqube.ws.client.project;
+import com.google.common.base.Joiner;
+import org.sonarqube.ws.WsProjects;
import org.sonarqube.ws.WsProjects.CreateWsResponse;
import org.sonarqube.ws.client.BaseService;
+import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;
+import static org.sonar.api.server.ws.WebService.Param.*;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
/**
* Maps web service {@code api/projects}.
@@ -47,7 +54,7 @@ public class ProjectsService extends BaseService {
*/
public CreateWsResponse create(CreateRequest project) {
PostRequest request = new PostRequest(path(ACTION_CREATE))
- .setParam("organization", project.getOrganization())
+ .setParam(PARAM_ORGANIZATION, project.getOrganization())
.setParam(PARAM_PROJECT, project.getKey())
.setParam(PARAM_NAME, project.getName())
.setParam(PARAM_BRANCH, project.getBranch());
@@ -62,4 +69,14 @@ public class ProjectsService extends BaseService {
.setParam("id", request.getId())
.setParam("key", request.getKey()));
}
+
+ public WsProjects.SearchWsResponse search(SearchWsRequest request) {
+ GetRequest get = new GetRequest(path(ACTION_SEARCH))
+ .setParam(PARAM_ORGANIZATION, request.getOrganization())
+ .setParam(PARAM_QUALIFIERS, Joiner.on(",").join(request.getQualifiers()))
+ .setParam(TEXT_QUERY, request.getQuery())
+ .setParam(PAGE, request.getPage())
+ .setParam(PAGE_SIZE, request.getPageSize());
+ return call(get, WsProjects.SearchWsResponse.parser());
+ }
}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
index 713618cbdba..b735334df8f 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
@@ -21,15 +21,20 @@ package org.sonarqube.ws.client.project;
public class ProjectsWsParameters {
+ public static final int MAX_PAGE_SIZE = 500;
+
public static final String CONTROLLER = "api/projects";
public static final String ACTION_CREATE = "create";
public static final String ACTION_INDEX = "index";
+ public static final String ACTION_SEARCH = "search";
public static final String PARAM_PROJECT = "project";
public static final String PARAM_PROJECT_ID = "projectId";
public static final String PARAM_NAME = "name";
public static final String PARAM_BRANCH = "branch";
+ public static final String PARAM_ORGANIZATION = "organization";
+ public static final String PARAM_QUALIFIERS = "qualifiers";
private ProjectsWsParameters() {
// static utils only
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java
new file mode 100644
index 00000000000..0bb627a9c61
--- /dev/null
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.project;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
+
+public class SearchWsRequest {
+
+ private final String organization;
+ private final String query;
+ private final List<String> qualifiers;
+ private final Integer page;
+ private final Integer pageSize;
+
+ public SearchWsRequest(Builder builder) {
+ this.organization = builder.organization;
+ this.query = builder.query;
+ this.qualifiers = builder.qualifiers;
+ this.page = builder.page;
+ this.pageSize = builder.pageSize;
+ }
+
+ @CheckForNull
+ public String getOrganization() {
+ return organization;
+ }
+
+ public List<String> getQualifiers() {
+ return qualifiers;
+ }
+
+ @CheckForNull
+ public Integer getPage() {
+ return page;
+ }
+
+ @CheckForNull
+ public Integer getPageSize() {
+ return pageSize;
+ }
+
+ @CheckForNull
+ public String getQuery() {
+ return query;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String organization;
+ private List<String> qualifiers = new ArrayList<>();
+ private Integer page;
+ private Integer pageSize;
+ private String query;
+
+ public Builder setOrganization(@Nullable String organization) {
+ this.organization = organization;
+ return this;
+ }
+
+ public Builder setQualifiers(List<String> qualifiers) {
+ this.qualifiers = requireNonNull(qualifiers, "Qualifiers cannot be null");
+ return this;
+ }
+
+ public Builder setPage(@Nullable Integer page) {
+ this.page = page;
+ return this;
+ }
+
+ public Builder setPageSize(@Nullable Integer pageSize) {
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ public Builder setQuery(@Nullable String query) {
+ this.query = query;
+ return this;
+ }
+
+ public SearchWsRequest build() {
+ checkArgument(pageSize == null || pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE);
+ return new SearchWsRequest(this);
+ }
+ }
+
+}
diff --git a/sonar-ws/src/main/protobuf/ws-projects.proto b/sonar-ws/src/main/protobuf/ws-projects.proto
index 64fb78379ac..7dcb6260857 100644
--- a/sonar-ws/src/main/protobuf/ws-projects.proto
+++ b/sonar-ws/src/main/protobuf/ws-projects.proto
@@ -57,3 +57,17 @@ message CreateWsResponse {
}
}
+// WS api/projects/search
+message SearchWsResponse {
+ optional sonarqube.ws.commons.Paging paging = 1;
+ repeated Component components = 2;
+
+ message Component {
+ optional string organization = 1;
+ optional string id = 2;
+ optional string key = 3;
+ optional string name = 4;
+ optional string qualifier = 5;
+ }
+}
+
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java
index b2fca0a05ba..2576eb3eb09 100644
--- a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java
@@ -25,9 +25,12 @@ import org.sonarqube.ws.WsProjects;
import org.sonarqube.ws.client.ServiceTester;
import org.sonarqube.ws.client.WsConnector;
+import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Mockito.mock;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
public class ProjectsServiceTest {
@@ -96,4 +99,24 @@ public class ProjectsServiceTest {
assertThat(serviceTester.getPostRequest().getPath()).isEqualTo("api/projects/delete");
assertThat(serviceTester.getPostRequest().getParams()).containsOnly(entry("key", "project_key"));
}
+
+ @Test
+ public void search() {
+ underTest.search(SearchWsRequest.builder()
+ .setOrganization("default")
+ .setQuery("project")
+ .setQualifiers(asList("TRK", "VW"))
+ .setPage(3)
+ .setPageSize(10)
+ .build());
+
+ serviceTester.assertThat(serviceTester.getGetRequest())
+ .hasPath("search")
+ .hasParam("organization", "default")
+ .hasParam("q", "project")
+ .hasParam("qualifiers", "TRK,VW")
+ .hasParam(PAGE, 3)
+ .hasParam(PAGE_SIZE, 10)
+ .andNoOtherParam();
+ }
}
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java
new file mode 100644
index 00000000000..89f27374b0a
--- /dev/null
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.ws.client.project;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SearchWsRequestTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void create_request() throws Exception {
+ SearchWsRequest underTest = SearchWsRequest.builder()
+ .setOrganization("orga")
+ .setQuery("project")
+ .setQualifiers(asList("TRK", "VW"))
+ .setPage(5)
+ .setPageSize(10)
+ .build();
+
+ assertThat(underTest.getOrganization()).isEqualTo("orga");
+ assertThat(underTest.getQuery()).isEqualTo("project");
+ assertThat(underTest.getQualifiers()).containsOnly("TRK", "VW");
+ assertThat(underTest.getPage()).isEqualTo(5);
+ assertThat(underTest.getPageSize()).isEqualTo(10);
+ }
+
+ @Test
+ public void fail_when_page_size_is_greather_then_500() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Page size must not be greater than 500");
+
+ SearchWsRequest.builder()
+ .setPageSize(10000)
+ .build();
+ }
+}