Pārlūkot izejas kodu

SONAR-11106 Restrict scope of api/issues/authors

* 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 IT
tags/7.5
Julien Lancelot pirms 5 gadiem
vecāks
revīzija
c9d8fb12af

+ 2
- 3
server/sonar-server-common/src/main/java/org/sonar/server/es/EsUtils.java Parādīt failu

@@ -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) {

+ 5
- 9
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java Parādīt failu

@@ -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);
}

+ 0
- 12
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueQuery.java Parādīt failu

@@ -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;

+ 97
- 29
server/sonar-server/src/main/java/org/sonar/server/issue/ws/AuthorsAction.java Parādīt failu

@@ -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));
}

}

+ 0
- 1
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java Parādīt failu

@@ -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");

+ 13
- 17
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java Parādīt failu

@@ -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

+ 322
- 20
server/sonar-server/src/test/java/org/sonar/server/issue/ws/AuthorsActionTest.java Parādīt failu

@@ -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);
}
}

+ 28
- 2
sonar-ws/src/main/java/org/sonarqube/ws/client/issues/AuthorsRequest.java Parādīt failu

@@ -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;

+ 7
- 5
sonar-ws/src/main/java/org/sonarqube/ws/client/issues/IssuesService.java Parādīt failu

@@ -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());

+ 5
- 6
sonar-ws/src/main/protobuf/ws-issues.proto Parādīt failu

@@ -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;
}




Notiek ielāde…
Atcelt
Saglabāt