* SONAR-11106 Sanitize api/issues/authors - Return PROTO and JSON response - Set max value to PAGE_SIZE parameter * SONAR-11106 Return only allowed authors * Remove no more needed IssueQuery#checkAuthorization * SONAR-11106 Add 'organization' to api/issues/authors - Return only authors from issues belonging to given organization - Check organization membership * SONAR-11106 Add 'project' to api/issues/authors * Move Muppet rule in the BillingTestSuite * SONAR-11106 Improve ITtags/7.5
@@ -75,13 +75,12 @@ public class EsUtils { | |||
} | |||
public static List<String> termsKeys(Terms terms) { | |||
return terms.getBuckets().stream() | |||
return terms.getBuckets() | |||
.stream() | |||
.map(Terms.Bucket::getKeyAsString) | |||
.collect(MoreCollectors.toList(terms.getBuckets().size())); | |||
} | |||
@CheckForNull | |||
public static Date parseDateTime(@Nullable String s) { | |||
if (s == null) { |
@@ -322,7 +322,7 @@ public class IssueIndex { | |||
private Map<String, QueryBuilder> createFilters(IssueQuery query) { | |||
Map<String, QueryBuilder> filters = new HashMap<>(); | |||
filters.put("__authorization", createAuthorizationFilter(query.checkAuthorization())); | |||
filters.put("__authorization", createAuthorizationFilter()); | |||
// Issue is assigned Filter | |||
if (BooleanUtils.isTrue(query.assigned())) { | |||
@@ -518,11 +518,8 @@ public class IssueIndex { | |||
return sorting.fillDefault(); | |||
} | |||
private QueryBuilder createAuthorizationFilter(boolean checkAuthorization) { | |||
if (checkAuthorization) { | |||
return authorizationTypeSupport.createQueryFilter(); | |||
} | |||
return matchAllQuery(); | |||
private QueryBuilder createAuthorizationFilter() { | |||
return authorizationTypeSupport.createQueryFilter(); | |||
} | |||
private void addDatesFilter(Map<String, QueryBuilder> filters, IssueQuery query) { | |||
@@ -705,8 +702,7 @@ public class IssueIndex { | |||
return emptyList(); | |||
} | |||
BoolQueryBuilder esQuery = boolQuery() | |||
.filter(createAuthorizationFilter(true)); | |||
BoolQueryBuilder esQuery = boolQuery().filter(createAuthorizationFilter()); | |||
if (organization != null) { | |||
esQuery.filter(termQuery(FIELD_ISSUE_ORGANIZATION_UUID, organization.getUuid())); | |||
} | |||
@@ -737,7 +733,7 @@ public class IssueIndex { | |||
return EsUtils.termsToMap(terms); | |||
} | |||
public List<String> listAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) { | |||
public List<String> searchAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) { | |||
Terms terms = listTermsMatching(FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, Terms.Order.term(true), maxNumberOfAuthors); | |||
return EsUtils.termsKeys(terms); | |||
} |
@@ -90,7 +90,6 @@ public class IssueQuery { | |||
private final String organizationUuid; | |||
private final String branchUuid; | |||
private final boolean mainBranch; | |||
private final boolean checkAuthorization; | |||
private IssueQuery(Builder builder) { | |||
this.issueKeys = defaultCollection(builder.issueKeys); | |||
@@ -122,7 +121,6 @@ public class IssueQuery { | |||
this.createdBefore = builder.createdBefore; | |||
this.sort = builder.sort; | |||
this.asc = builder.asc; | |||
this.checkAuthorization = builder.checkAuthorization; | |||
this.facetMode = builder.facetMode; | |||
this.organizationUuid = builder.organizationUuid; | |||
this.branchUuid = builder.branchUuid; | |||
@@ -253,10 +251,6 @@ public class IssueQuery { | |||
return asc; | |||
} | |||
public boolean checkAuthorization() { | |||
return checkAuthorization; | |||
} | |||
@CheckForNull | |||
public String organizationUuid() { | |||
return organizationUuid; | |||
@@ -314,7 +308,6 @@ public class IssueQuery { | |||
private Date createdBefore; | |||
private String sort; | |||
private Boolean asc = false; | |||
private boolean checkAuthorization = true; | |||
private String facetMode; | |||
private String organizationUuid; | |||
private String branchUuid; | |||
@@ -496,11 +489,6 @@ public class IssueQuery { | |||
return new IssueQuery(this); | |||
} | |||
public Builder checkAuthorization(boolean checkAuthorization) { | |||
this.checkAuthorization = checkAuthorization; | |||
return this; | |||
} | |||
public Builder facetMode(String facetMode) { | |||
this.facetMode = facetMode; | |||
return this; |
@@ -19,66 +19,134 @@ | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.io.Resources; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.resources.Scopes; | |||
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.NewAction; | |||
import org.sonar.api.server.ws.WebService.Param; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.server.issue.index.IssueQuery; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueQuery; | |||
import org.sonar.server.organization.DefaultOrganizationProvider; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Issues.AuthorsResponse; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_AUTHORS; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.util.Optional.ofNullable; | |||
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.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; | |||
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
public class AuthorsAction implements IssuesWsAction { | |||
private static final String PARAM_ORGANIZATION = "organization"; | |||
private static final String PARAM_PROJECT = "project"; | |||
private final UserSession userSession; | |||
private final DbClient dbClient; | |||
private final IssueIndex issueIndex; | |||
private final ComponentFinder componentFinder; | |||
private final DefaultOrganizationProvider defaultOrganizationProvider; | |||
public AuthorsAction(IssueIndex issueIndex) { | |||
public AuthorsAction(UserSession userSession, DbClient dbClient, IssueIndex issueIndex, ComponentFinder componentFinder, | |||
DefaultOrganizationProvider defaultOrganizationProvider) { | |||
this.userSession = userSession; | |||
this.dbClient = dbClient; | |||
this.issueIndex = issueIndex; | |||
this.componentFinder = componentFinder; | |||
this.defaultOrganizationProvider = defaultOrganizationProvider; | |||
} | |||
@Override | |||
public void define(WebService.NewController controller) { | |||
NewAction action = controller.createAction(ACTION_AUTHORS) | |||
NewAction action = controller.createAction("authors") | |||
.setSince("5.1") | |||
.setDescription("Search SCM accounts which match a given query") | |||
.setDescription("Search SCM accounts which match a given query.<br/>" + | |||
"Requires authentication.") | |||
.setResponseExample(Resources.getResource(this.getClass(), "authors-example.json")) | |||
.setChangelog(new Change("7.4", "The maximum size of 'ps' is set to 100")) | |||
.setHandler(this); | |||
action.createParam(Param.TEXT_QUERY) | |||
.setDescription("A pattern to match SCM accounts against") | |||
.setExampleValue("luke"); | |||
action.createParam(Param.PAGE_SIZE) | |||
.setDescription("The size of the list to return") | |||
.setExampleValue("25") | |||
.setDefaultValue("10"); | |||
action.createSearchQuery("luke", "authors"); | |||
action.createPageSize(10, 100); | |||
action.createParam(PARAM_ORGANIZATION) | |||
.setDescription("Organization key") | |||
.setRequired(false) | |||
.setInternal(true) | |||
.setExampleValue("my-org") | |||
.setSince("7.4"); | |||
action.createParam(PARAM_PROJECT) | |||
.setDescription("Project key") | |||
.setRequired(false) | |||
.setExampleValue(KEY_PROJECT_EXAMPLE_001) | |||
.setSince("7.4"); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
String query = request.param(Param.TEXT_QUERY); | |||
int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE); | |||
try (JsonWriter json = response.newJsonWriter()) { | |||
json.beginObject() | |||
.name("authors") | |||
.beginArray(); | |||
userSession.checkLoggedIn(); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
OrganizationDto organization = getOrganization(dbSession, request.param(PARAM_ORGANIZATION)); | |||
userSession.checkMembership(organization); | |||
Optional<ComponentDto> project = getProject(dbSession, organization, request.param(PARAM_PROJECT)); | |||
List<String> authors = getAuthors(organization, project, request); | |||
AuthorsResponse wsResponse = AuthorsResponse.newBuilder().addAllAuthors(authors).build(); | |||
writeProtobuf(wsResponse, request, response); | |||
} | |||
} | |||
for (String login : listAuthors(query, pageSize)) { | |||
json.value(login); | |||
} | |||
private OrganizationDto getOrganization(DbSession dbSession, @Nullable String organizationKey) { | |||
String organizationOrDefaultKey = ofNullable(organizationKey).orElseGet(defaultOrganizationProvider.get()::getKey); | |||
return checkFoundWithOptional( | |||
dbClient.organizationDao().selectByKey(dbSession, organizationOrDefaultKey), | |||
"No organization with key '%s'", organizationOrDefaultKey); | |||
} | |||
json.endArray().endObject(); | |||
private Optional<ComponentDto> getProject(DbSession dbSession, OrganizationDto organization, @Nullable String projectKey) { | |||
if (projectKey == null) { | |||
return Optional.empty(); | |||
} | |||
ComponentDto project = componentFinder.getByKey(dbSession, projectKey); | |||
checkArgument(project.scope().equals(Scopes.PROJECT), "Component '%s' must be a project", projectKey); | |||
checkArgument(project.getOrganizationUuid().equals(organization.getUuid()), "Project '%s' is not part of the organization '%s'", projectKey, organization.getKey()); | |||
return Optional.of(project); | |||
} | |||
public List<String> listAuthors(@Nullable String textQuery, int pageSize) { | |||
return issueIndex.listAuthors(IssueQuery.builder() | |||
.checkAuthorization(false) | |||
.build(), textQuery, pageSize); | |||
private List<String> getAuthors(OrganizationDto organization, Optional<ComponentDto> project, Request request) { | |||
IssueQuery.Builder issueQueryBuilder = IssueQuery.builder() | |||
.organizationUuid(organization.getUuid()); | |||
project.ifPresent(p -> { | |||
switch (p.qualifier()) { | |||
case Qualifiers.PROJECT: | |||
issueQueryBuilder.projectUuids(ImmutableSet.of(p.uuid())); | |||
return; | |||
case Qualifiers.APP: | |||
case Qualifiers.VIEW: | |||
issueQueryBuilder.viewUuids(ImmutableSet.of(p.uuid())); | |||
return; | |||
default: | |||
throw new IllegalArgumentException(String.format("Component of type '%s' is not supported", p.qualifier())); | |||
} | |||
}); | |||
return issueIndex.searchAuthors( | |||
issueQueryBuilder.build(), | |||
request.param(TEXT_QUERY), | |||
request.mandatoryParamAsInt(PAGE_SIZE)); | |||
} | |||
} |
@@ -356,7 +356,6 @@ public class IssueIndexFacetsTest { | |||
IssueQuery query = IssueQuery.builder() | |||
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100")) | |||
.createdBefore(parseDateTime("2014-09-08T00:00:00+0100")) | |||
.checkAuthorization(false) | |||
.build(); | |||
SearchResponse result = underTest.search(query, options); | |||
Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone()).get("createdAt"); |
@@ -254,15 +254,13 @@ public class IssueIndexTest { | |||
newDoc("issue2", project).setAuthorLogin("luke@skywalker.name"), | |||
newDoc("issue3", project).setAuthorLogin(null), | |||
newDoc("issue4", project).setAuthorLogin("anakin@skywalker.name")); | |||
IssueQuery query = IssueQuery.builder() | |||
.checkAuthorization(false) | |||
.build(); | |||
assertThat(underTest.listAuthors(query, null, 5)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name"); | |||
assertThat(underTest.listAuthors(query, null, 2)).containsExactly("anakin@skywalker.name", "luke.skywalker"); | |||
assertThat(underTest.listAuthors(query, "uke", 5)).containsExactly("luke.skywalker", "luke@skywalker.name"); | |||
assertThat(underTest.listAuthors(query, null, 1)).containsExactly("anakin@skywalker.name"); | |||
assertThat(underTest.listAuthors(query, null, Integer.MAX_VALUE)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name"); | |||
IssueQuery query = IssueQuery.builder().build(); | |||
assertThat(underTest.searchAuthors(query, null, 5)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name"); | |||
assertThat(underTest.searchAuthors(query, null, 2)).containsExactly("anakin@skywalker.name", "luke.skywalker"); | |||
assertThat(underTest.searchAuthors(query, "uke", 5)).containsExactly("luke.skywalker", "luke@skywalker.name"); | |||
assertThat(underTest.searchAuthors(query, null, 1)).containsExactly("anakin@skywalker.name"); | |||
assertThat(underTest.searchAuthors(query, null, Integer.MAX_VALUE)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name"); | |||
} | |||
@Test | |||
@@ -271,14 +269,12 @@ public class IssueIndexTest { | |||
ComponentDto project = newPrivateProjectDto(org); | |||
indexIssues( | |||
newDoc("issue1", project).setAuthorLogin("name++")); | |||
IssueQuery query = IssueQuery.builder() | |||
.checkAuthorization(false) | |||
.build(); | |||
assertThat(underTest.listAuthors(query, "invalidRegexp[", 5)).isEmpty(); | |||
assertThat(underTest.listAuthors(query, "nam+", 5)).isEmpty(); | |||
assertThat(underTest.listAuthors(query, "name+", 5)).containsExactly("name++"); | |||
assertThat(underTest.listAuthors(query, ".*", 5)).isEmpty(); | |||
IssueQuery query = IssueQuery.builder().build(); | |||
assertThat(underTest.searchAuthors(query, "invalidRegexp[", 5)).isEmpty(); | |||
assertThat(underTest.searchAuthors(query, "nam+", 5)).isEmpty(); | |||
assertThat(underTest.searchAuthors(query, "name+", 5)).containsExactly("name++"); | |||
assertThat(underTest.searchAuthors(query, ".*", 5)).isEmpty(); | |||
} | |||
@Test |
@@ -21,21 +21,42 @@ package org.sonar.server.issue.ws; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.WebService.Param; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ResourceTypesRule; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.exceptions.UnauthorizedException; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueIndexer; | |||
import org.sonar.server.issue.index.IssueIteratorFactory; | |||
import org.sonar.server.organization.DefaultOrganizationProvider; | |||
import org.sonar.server.organization.TestDefaultOrganizationProvider; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
import org.sonar.server.permission.index.WebAuthorizationTypeSupport; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.view.index.ViewIndexer; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonarqube.ws.Issues.AuthorsResponse; | |||
import static java.lang.String.format; | |||
import static java.util.Collections.emptySet; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
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.db.component.ComponentTesting.newFileDto; | |||
import static org.sonar.db.component.ComponentTesting.newProjectCopy; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
public class AuthorsActionTest { | |||
@@ -45,36 +66,180 @@ public class AuthorsActionTest { | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient())); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient())); | |||
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer); | |||
private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client()); | |||
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); | |||
private ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT); | |||
private WsActionTester ws = new WsActionTester(new AuthorsAction(issueIndex)); | |||
private WsActionTester ws = new WsActionTester(new AuthorsAction(userSession, db.getDbClient(), issueIndex, | |||
new ComponentFinder(db.getDbClient(), resourceTypes), defaultOrganizationProvider)); | |||
@Test | |||
public void json_example() { | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin("luke.skywalker")); | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin("leia.organa")); | |||
public void search_authors() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(db.getDefaultOrganization()); | |||
String result = ws.newRequest().execute().getInput(); | |||
AuthorsResponse result = ws.newRequest().executeProtobuf(AuthorsResponse.class); | |||
assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); | |||
assertThat(result.getAuthorsList()).containsExactlyInAnyOrder(leia, luke); | |||
} | |||
@Test | |||
public void search_by_query() { | |||
public void search_authors_by_query() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin(luke)); | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(db.getDefaultOrganization()); | |||
String result = ws.newRequest() | |||
AuthorsResponse result = ws.newRequest() | |||
.setParam(TEXT_QUERY, "leia") | |||
.execute().getInput(); | |||
.executeProtobuf(AuthorsResponse.class); | |||
assertThat(result.getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia) | |||
.doesNotContain(luke); | |||
} | |||
@Test | |||
public void search_authors_by_organization() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
OrganizationDto organization1 = db.organizations().insert(); | |||
OrganizationDto organization2 = db.organizations().insert(); | |||
ComponentDto project1 = db.components().insertPrivateProject(organization1); | |||
ComponentDto project2 = db.components().insertPrivateProject(organization2); | |||
permissionIndexer.allowOnlyAnyone(project1, project2); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project1, project1, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project2, project2, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(organization1); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization1.getKey()) | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization1.getKey()) | |||
.setParam(TEXT_QUERY, "eia") | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization1.getKey()) | |||
.setParam(TEXT_QUERY, "luke") | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.isEmpty(); | |||
} | |||
@Test | |||
public void search_authors_by_project() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
OrganizationDto organization = db.organizations().insert(); | |||
ComponentDto project1 = db.components().insertPrivateProject(organization); | |||
ComponentDto project2 = db.components().insertPrivateProject(organization); | |||
permissionIndexer.allowOnlyAnyone(project1, project2); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project1, project1, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project2, project2, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(organization); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", project1.getKey()) | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", project1.getKey()) | |||
.setParam(TEXT_QUERY, "eia") | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
assertThat(ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", project1.getKey()) | |||
.setParam(TEXT_QUERY, "luke") | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.isEmpty(); | |||
} | |||
@Test | |||
public void search_authors_by_portfolio() { | |||
String leia = "leia.organa"; | |||
OrganizationDto organization = db.getDefaultOrganization(); | |||
ComponentDto portfolio = db.components().insertPrivatePortfolio(organization); | |||
ComponentDto project = db.components().insertPrivateProject(organization); | |||
db.components().insertComponent(newProjectCopy(project, portfolio)); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
viewIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(organization); | |||
assertThat(ws.newRequest() | |||
.setParam("project", portfolio.getKey()) | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
} | |||
@Test | |||
public void search_authors_by_application() { | |||
String leia = "leia.organa"; | |||
OrganizationDto defaultOrganization = db.getDefaultOrganization(); | |||
ComponentDto application = db.components().insertPrivateApplication(defaultOrganization); | |||
ComponentDto project = db.components().insertPrivateProject(defaultOrganization); | |||
db.components().insertComponent(newProjectCopy(project, application)); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
viewIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(defaultOrganization); | |||
assertThat(result).contains(leia).doesNotContain(luke); | |||
assertThat(ws.newRequest() | |||
.setParam("project", application.getKey()) | |||
.executeProtobuf(AuthorsResponse.class).getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia); | |||
} | |||
@Test | |||
public void default_organization_is_used_when_no_organization_parameter_set() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
ComponentDto project1 = db.components().insertPrivateProject(db.getDefaultOrganization()); | |||
OrganizationDto otherOrganization = db.organizations().insert(); | |||
ComponentDto project2 = db.components().insertPrivateProject(otherOrganization); | |||
permissionIndexer.allowOnlyAnyone(project1, project2); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project1, project1, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project2, project2, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(db.getDefaultOrganization()); | |||
AuthorsResponse result = ws.newRequest().executeProtobuf(AuthorsResponse.class); | |||
assertThat(result.getAuthorsList()) | |||
.containsExactlyInAnyOrder(leia) | |||
.doesNotContain(luke); | |||
} | |||
@Test | |||
@@ -82,16 +247,141 @@ public class AuthorsActionTest { | |||
String han = "han.solo"; | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin(han)); | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insertIssue(issue -> issue.setAuthorLogin(luke)); | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(han)); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(db.getDefaultOrganization()); | |||
String result = ws.newRequest() | |||
AuthorsResponse result = ws.newRequest() | |||
.setParam(PAGE_SIZE, "2") | |||
.execute().getInput(); | |||
.executeProtobuf(AuthorsResponse.class); | |||
assertThat(result).contains(han, leia).doesNotContain(luke); | |||
assertThat(result.getAuthorsList()) | |||
.containsExactlyInAnyOrder(han, leia) | |||
.doesNotContain(luke); | |||
} | |||
@Test | |||
public void return_only_authors_from_issues_visible_by_current_user() { | |||
String leia = "leia.organa"; | |||
String luke = "luke.skywalker"; | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
UserDto user = db.users().insertUser(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(leia)); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin(luke)); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn(user).addMembership(db.getDefaultOrganization()); | |||
// User has no permission on project | |||
assertThat(ws.newRequest().executeProtobuf(AuthorsResponse.class).getAuthorsList()).isEmpty(); | |||
// User has no browse permission on project | |||
permissionIndexer.allowOnlyUser(project, user); | |||
assertThat(ws.newRequest().executeProtobuf(AuthorsResponse.class).getAuthorsList()).isNotEmpty(); | |||
} | |||
@Test | |||
public void fail_when_user_is_not_logged() { | |||
userSession.anonymous(); | |||
expectedException.expect(UnauthorizedException.class); | |||
ws.newRequest().execute(); | |||
} | |||
@Test | |||
public void fail_when_user_is_not_member_of_the_organization() { | |||
OrganizationDto organization = db.organizations().insert(); | |||
OrganizationDto otherOrganization = db.organizations().insert(); | |||
userSession.logIn().addMembership(otherOrganization); | |||
expectedException.expect(ForbiddenException.class); | |||
ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.execute(); | |||
} | |||
@Test | |||
public void fail_when_organization_does_not_exist() { | |||
userSession.logIn(); | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("No organization with key 'unknown'"); | |||
ws.newRequest() | |||
.setParam("organization", "unknown") | |||
.execute(); | |||
} | |||
@Test | |||
public void fail_when_project_does_not_belong_to_organization() { | |||
OrganizationDto organization = db.organizations().insert(); | |||
OrganizationDto otherOrganization = db.organizations().insert(); | |||
userSession.logIn() | |||
.addMembership(organization) | |||
.addMembership(otherOrganization); | |||
ComponentDto project = db.components().insertPrivateProject(otherOrganization); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage(format("Project '%s' is not part of the organization '%s'", project.getKey(), organization.getKey())); | |||
ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", project.getKey()) | |||
.execute(); | |||
} | |||
@Test | |||
public void fail_when_project_is_not_a_project() { | |||
OrganizationDto organization = db.organizations().insert(); | |||
userSession.logIn().addMembership(organization); | |||
ComponentDto project = db.components().insertPrivateProject(organization); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage(format("Component '%s' must be a project", file.getKey())); | |||
ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", file.getKey()) | |||
.execute(); | |||
} | |||
@Test | |||
public void fail_when_project_does_not_exist() { | |||
OrganizationDto organization = db.organizations().insert(); | |||
userSession.logIn().addMembership(organization); | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("Component key 'unknown' not found"); | |||
ws.newRequest() | |||
.setParam("organization", organization.getKey()) | |||
.setParam("project", "unknown") | |||
.execute(); | |||
} | |||
@Test | |||
public void json_example() { | |||
ComponentDto project = db.components().insertPrivateProject(); | |||
permissionIndexer.allowOnlyAnyone(project); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin("luke.skywalker")); | |||
db.issues().insert(rule, project, project, issue -> issue.setAuthorLogin("leia.organa")); | |||
issueIndexer.indexOnStartup(emptySet()); | |||
userSession.logIn().addMembership(db.getDefaultOrganization()); | |||
String result = ws.newRequest().execute().getInput(); | |||
assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); | |||
} | |||
@Test | |||
@@ -100,8 +390,20 @@ public class AuthorsActionTest { | |||
assertThat(definition.key()).isEqualTo("authors"); | |||
assertThat(definition.since()).isEqualTo("5.1"); | |||
assertThat(definition.isPost()).isFalse(); | |||
assertThat(definition.isInternal()).isFalse(); | |||
assertThat(definition.responseExampleAsString()).isNotEmpty(); | |||
assertThat(definition.params()).extracting(WebService.Param::key).containsOnly("q", "ps"); | |||
assertThat(definition.params()) | |||
.extracting(Param::key, Param::isRequired, Param::isInternal) | |||
.containsExactlyInAnyOrder( | |||
tuple("q", false, false), | |||
tuple("ps", false, false), | |||
tuple("organization", false, true), | |||
tuple("project", false, false)); | |||
assertThat(definition.param("ps")) | |||
.extracting(Param::defaultValue, Param::maximumValue) | |||
.containsExactly("10", 100); | |||
} | |||
} |
@@ -19,7 +19,6 @@ | |||
*/ | |||
package org.sonarqube.ws.client.issues; | |||
import java.util.List; | |||
import javax.annotation.Generated; | |||
/** | |||
@@ -31,11 +30,38 @@ import javax.annotation.Generated; | |||
@Generated("sonar-ws-generator") | |||
public class AuthorsRequest { | |||
private String organization; | |||
private String project; | |||
private String ps; | |||
private String q; | |||
/** | |||
* Example value: "25" | |||
* This is part of the internal API. | |||
* Example value: "my-org" | |||
*/ | |||
public AuthorsRequest setOrganization(String organization) { | |||
this.organization = organization; | |||
return this; | |||
} | |||
public String getOrganization() { | |||
return organization; | |||
} | |||
/** | |||
* Example value: "my_project" | |||
*/ | |||
public AuthorsRequest setProject(String project) { | |||
this.project = project; | |||
return this; | |||
} | |||
public String getProject() { | |||
return project; | |||
} | |||
/** | |||
* Example value: "20" | |||
*/ | |||
public AuthorsRequest setPs(String ps) { | |||
this.ps = ps; |
@@ -21,11 +21,6 @@ package org.sonarqube.ws.client.issues; | |||
import java.util.stream.Collectors; | |||
import javax.annotation.Generated; | |||
import org.sonarqube.ws.MediaTypes; | |||
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 org.sonarqube.ws.Issues.AddCommentResponse; | |||
import org.sonarqube.ws.Issues.AssignResponse; | |||
import org.sonarqube.ws.Issues.AuthorsResponse; | |||
@@ -38,6 +33,11 @@ import org.sonarqube.ws.Issues.SetSeverityResponse; | |||
import org.sonarqube.ws.Issues.SetTagsResponse; | |||
import org.sonarqube.ws.Issues.SetTypeResponse; | |||
import org.sonarqube.ws.Issues.TagsResponse; | |||
import org.sonarqube.ws.MediaTypes; | |||
import org.sonarqube.ws.client.BaseService; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.PostRequest; | |||
import org.sonarqube.ws.client.WsConnector; | |||
/** | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/issues">Further information about this web service online</a> | |||
@@ -90,6 +90,8 @@ public class IssuesService extends BaseService { | |||
public AuthorsResponse authors(AuthorsRequest request) { | |||
return call( | |||
new GetRequest(path("authors")) | |||
.setParam("organization", request.getOrganization()) | |||
.setParam("project", request.getProject()) | |||
.setParam("ps", request.getPs()) | |||
.setParam("q", request.getQ()), | |||
AuthorsResponse.parser()); |
@@ -67,12 +67,6 @@ message AssignResponse { | |||
repeated sonarqube.ws.commons.Rule rules = 3; | |||
repeated Users.User users = 4; | |||
} | |||
message AuthorsResponse { | |||
optional Issue issue = 1; | |||
repeated Component components = 2; | |||
repeated sonarqube.ws.commons.Rule rules = 3; | |||
repeated Users.User users = 4; | |||
} | |||
message DeleteCommentResponse { | |||
optional Issue issue = 1; | |||
repeated Component components = 2; | |||
@@ -282,5 +276,10 @@ message Users { | |||
} | |||
} | |||
// Response of GET api/issues/authors | |||
message AuthorsResponse { | |||
repeated string authors = 1; | |||
} | |||