/* * SonarQube * Copyright (C) 2009-2018 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.organization.ws; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.server.ws.Change; 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.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.alm.OrganizationAlmBindingDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationQuery; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Organizations; import org.sonarqube.ws.Organizations.Organization; import static java.lang.String.format; import static java.util.Collections.emptySet; import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.core.util.stream.MoreCollectors.toSet; import static org.sonar.db.Pagination.forPage; import static org.sonar.db.organization.OrganizationQuery.newOrganizationQueryBuilder; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.Common.Paging; public class SearchAction implements OrganizationsWsAction { private static final String PARAM_ORGANIZATIONS = "organizations"; static final String PARAM_MEMBER = "member"; private static final String ACTION = "search"; private static final int MAX_SIZE = 500; private final DbClient dbClient; private final UserSession userSession; public SearchAction(DbClient dbClient, UserSession userSession) { this.dbClient = dbClient; this.userSession = userSession; } @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(ACTION) .setPost(false) .setDescription("Search for organizations") .setResponseExample(getClass().getResource("search-example.json")) .setInternal(true) .setSince("6.2") .setChangelog(new Change("7.5", format("Return 'subscription' field when parameter '%s' is set to 'true'", PARAM_MEMBER))) .setChangelog(new Change("7.5", "Removed 'isAdmin' and return 'actions' for each organization")) .setChangelog(new Change("6.4", "Paging fields have been added to the response")) .setHandler(this); action.createParam(PARAM_ORGANIZATIONS) .setDescription("Comma-separated list of organization keys") .setExampleValue(String.join(",", "my-org-1", "foocorp")) .setMaxValuesAllowed(MAX_SIZE) .setRequired(false) .setSince("6.3"); action.createParam(PARAM_MEMBER) .setDescription("Filter organizations based on whether the authenticated user is a member. If false, no filter applies.") .setSince("7.0") .setDefaultValue("false") .setBooleanPossibleValues(); action.addPagingParams(100, MAX_SIZE); } @Override public void handle(Request request, Response response) throws Exception { boolean onlyMembershipOrganizations = request.mandatoryParamAsBoolean(PARAM_MEMBER); if (onlyMembershipOrganizations) { userSession.checkLoggedIn(); } try (DbSession dbSession = dbClient.openSession(false)) { OrganizationQuery dbQuery = buildDbQuery(request); int total = dbClient.organizationDao().countByQuery(dbSession, dbQuery); Paging paging = buildWsPaging(request, total); List organizations = dbClient.organizationDao().selectByQuery(dbSession, dbQuery, forPage(paging.getPageIndex()).andSize(paging.getPageSize())); Set adminOrganizationUuids = searchOrganizationWithAdminPermission(dbSession); Set provisionOrganizationUuids = searchOrganizationWithProvisionPermission(dbSession); Map organizationAlmBindingByOrgUuid = dbClient.organizationAlmBindingDao().selectByOrganizations(dbSession, organizations) .stream().collect(MoreCollectors.uniqueIndex(OrganizationAlmBindingDto::getOrganizationUuid)); Organizations.SearchWsResponse wsResponse = buildOrganizations(organizations, adminOrganizationUuids, provisionOrganizationUuids, organizationAlmBindingByOrgUuid, onlyMembershipOrganizations, paging); writeProtobuf(wsResponse, request, response); } } private OrganizationQuery buildDbQuery(Request request) { return newOrganizationQueryBuilder() .setKeys(request.paramAsStrings(PARAM_ORGANIZATIONS)) .setMember(getUserIdIfFilterOnMembership(request)) .build(); } private Set searchOrganizationWithAdminPermission(DbSession dbSession) { Integer userId = userSession.getUserId(); return userId == null ? emptySet() : dbClient.organizationDao().selectByPermission(dbSession, userId, ADMINISTER.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet()); } private Set searchOrganizationWithProvisionPermission(DbSession dbSession) { Integer userId = userSession.getUserId(); return userId == null ? emptySet() : dbClient.organizationDao().selectByPermission(dbSession, userId, PROVISION_PROJECTS.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet()); } private Organizations.SearchWsResponse buildOrganizations(List organizations, Set adminOrganizationUuids, Set provisionOrganizationUuids, Map organizationAlmBindingByOrgUuid, boolean onlyMembershipOrganizations, Paging paging) { Organizations.SearchWsResponse.Builder response = Organizations.SearchWsResponse.newBuilder(); response.setPaging(paging); Organization.Builder wsOrganization = Organization.newBuilder(); organizations .forEach(o -> { wsOrganization.clear(); boolean isAdmin = userSession.isRoot() || adminOrganizationUuids.contains(o.getUuid()); boolean canProvision = userSession.isRoot() || provisionOrganizationUuids.contains(o.getUuid()); wsOrganization.setActions(Organization.Actions.newBuilder() .setAdmin(isAdmin) .setProvision(canProvision) .setDelete(o.isGuarded() ? userSession.isRoot() : isAdmin)); response.addOrganizations(toOrganization(wsOrganization, o, organizationAlmBindingByOrgUuid.get(o.getUuid()), onlyMembershipOrganizations)); }); return response.build(); } private static Organization.Builder toOrganization(Organization.Builder builder, OrganizationDto organization, @Nullable OrganizationAlmBindingDto organizationAlmBinding, boolean onlyMembershipOrganizations) { builder .setName(organization.getName()) .setKey(organization.getKey()) .setGuarded(organization.isGuarded()); setNullable(organization.getDescription(), builder::setDescription); setNullable(organization.getUrl(), builder::setUrl); setNullable(organization.getAvatarUrl(), builder::setAvatar); if (organizationAlmBinding != null) { builder.setAlm(Organization.Alm.newBuilder() .setKey(organizationAlmBinding.getAlm().getId()) .setUrl(organizationAlmBinding.getUrl())); } if (onlyMembershipOrganizations) { builder.setSubscription(Organizations.Subscription.valueOf(organization.getSubscription().name())); } return builder; } private static Paging buildWsPaging(Request request, int total) { return Paging.newBuilder() .setPageIndex(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setTotal(total) .build(); } @CheckForNull private Integer getUserIdIfFilterOnMembership(Request request) { boolean filterOnAuthenticatedUser = request.mandatoryParamAsBoolean(PARAM_MEMBER); return (userSession.isLoggedIn() && filterOnAuthenticatedUser) ? userSession.getUserId() : null; } }