LCOV - code coverage report
Current view: top level - server/restapi/account - QueryAccounts.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 76 86 88.4 %
Date: 2022-11-19 15:00:39 Functions: 7 8 87.5 %

          Line data    Source code
       1             : // Copyright (C) 2014 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.restapi.account;
      16             : 
      17             : import com.google.common.base.Strings;
      18             : import com.google.common.collect.ImmutableList;
      19             : import com.google.gerrit.entities.Account;
      20             : import com.google.gerrit.extensions.client.ListAccountsOption;
      21             : import com.google.gerrit.extensions.client.ListOption;
      22             : import com.google.gerrit.extensions.common.AccountInfo;
      23             : import com.google.gerrit.extensions.common.AccountVisibility;
      24             : import com.google.gerrit.extensions.restapi.BadRequestException;
      25             : import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
      26             : import com.google.gerrit.extensions.restapi.Response;
      27             : import com.google.gerrit.extensions.restapi.RestApiException;
      28             : import com.google.gerrit.extensions.restapi.RestReadView;
      29             : import com.google.gerrit.extensions.restapi.TopLevelResource;
      30             : import com.google.gerrit.index.query.Predicate;
      31             : import com.google.gerrit.index.query.QueryParseException;
      32             : import com.google.gerrit.index.query.QueryResult;
      33             : import com.google.gerrit.server.account.AccountDirectory.FillOptions;
      34             : import com.google.gerrit.server.account.AccountInfoComparator;
      35             : import com.google.gerrit.server.account.AccountLoader;
      36             : import com.google.gerrit.server.account.AccountState;
      37             : import com.google.gerrit.server.config.GerritServerConfig;
      38             : import com.google.gerrit.server.permissions.GlobalPermission;
      39             : import com.google.gerrit.server.permissions.PermissionBackend;
      40             : import com.google.gerrit.server.permissions.PermissionBackendException;
      41             : import com.google.gerrit.server.query.account.AccountPredicates;
      42             : import com.google.gerrit.server.query.account.AccountQueryBuilder;
      43             : import com.google.gerrit.server.query.account.AccountQueryProcessor;
      44             : import com.google.inject.Inject;
      45             : import com.google.inject.Provider;
      46             : import java.util.Collections;
      47             : import java.util.EnumSet;
      48             : import java.util.LinkedHashMap;
      49             : import java.util.List;
      50             : import java.util.Map;
      51             : import java.util.Set;
      52             : import org.eclipse.jgit.lib.Config;
      53             : import org.kohsuke.args4j.Option;
      54             : 
      55             : /**
      56             :  * REST endpoint to query accounts.
      57             :  *
      58             :  * <p>This REST endpoint handles {@code GET /accounts/} requests.
      59             :  *
      60             :  * <p>The account queries are parsed by {@link AccountQueryBuilder} and executed by {@link
      61             :  * AccountQueryProcessor}.
      62             :  */
      63             : public class QueryAccounts implements RestReadView<TopLevelResource> {
      64             :   private static final int MAX_SUGGEST_RESULTS = 100;
      65             : 
      66             :   private final PermissionBackend permissionBackend;
      67             :   private final AccountLoader.Factory accountLoaderFactory;
      68             :   private final AccountQueryBuilder queryBuilder;
      69             :   private final Provider<AccountQueryProcessor> queryProcessorProvider;
      70             :   private final boolean suggestConfig;
      71             :   private final int suggestFrom;
      72             : 
      73             :   private AccountLoader accountLoader;
      74             :   private boolean suggest;
      75             :   private Integer limit;
      76           7 :   private int suggestLimit = 10;
      77             :   private String query;
      78             :   private Integer start;
      79             :   private EnumSet<ListAccountsOption> options;
      80             : 
      81             :   @Option(name = "--suggest", metaVar = "SUGGEST", usage = "suggest users")
      82             :   public void setSuggest(boolean suggest) {
      83           5 :     this.suggest = suggest;
      84           5 :   }
      85             : 
      86             :   @Option(
      87             :       name = "--limit",
      88             :       aliases = {"-n"},
      89             :       metaVar = "CNT",
      90             :       usage = "maximum number of users to return")
      91             :   public void setLimit(int n) {
      92           5 :     this.limit = n;
      93             : 
      94           5 :     if (n < 0) {
      95           0 :       suggestLimit = 10;
      96           5 :     } else if (n == 0) {
      97           5 :       suggestLimit = MAX_SUGGEST_RESULTS;
      98             :     } else {
      99           2 :       suggestLimit = Math.min(n, MAX_SUGGEST_RESULTS);
     100             :     }
     101           5 :   }
     102             : 
     103             :   @Option(name = "-o", usage = "Output options per account")
     104             :   public void addOption(ListAccountsOption o) {
     105           2 :     options.add(o);
     106           2 :   }
     107             : 
     108             :   @Option(name = "-O", usage = "Output option flags, in hex")
     109             :   void setOptionFlagsHex(String hex) throws BadRequestException {
     110           0 :     options.addAll(ListOption.fromHexString(ListAccountsOption.class, hex));
     111           0 :   }
     112             : 
     113             :   @Option(
     114             :       name = "--query",
     115             :       aliases = {"-q"},
     116             :       metaVar = "QUERY",
     117             :       usage = "match users")
     118             :   public void setQuery(String query) {
     119           6 :     this.query = query;
     120           6 :   }
     121             : 
     122             :   @Option(
     123             :       name = "--start",
     124             :       aliases = {"-S"},
     125             :       metaVar = "CNT",
     126             :       usage = "Number of accounts to skip")
     127             :   public void setStart(int start) {
     128           5 :     this.start = start;
     129           5 :   }
     130             : 
     131             :   @Inject
     132             :   QueryAccounts(
     133             :       PermissionBackend permissionBackend,
     134             :       AccountLoader.Factory accountLoaderFactory,
     135             :       AccountQueryBuilder queryBuilder,
     136             :       Provider<AccountQueryProcessor> queryProcessorProvider,
     137           7 :       @GerritServerConfig Config cfg) {
     138           7 :     this.permissionBackend = permissionBackend;
     139           7 :     this.accountLoaderFactory = accountLoaderFactory;
     140           7 :     this.queryBuilder = queryBuilder;
     141           7 :     this.queryProcessorProvider = queryProcessorProvider;
     142           7 :     this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
     143           7 :     this.options = EnumSet.noneOf(ListAccountsOption.class);
     144             : 
     145           7 :     if ("off".equalsIgnoreCase(cfg.getString("suggest", null, "accounts"))) {
     146           0 :       suggestConfig = false;
     147             :     } else {
     148             :       boolean suggest;
     149             :       try {
     150           7 :         AccountVisibility av = cfg.getEnum("suggest", null, "accounts", AccountVisibility.ALL);
     151           7 :         suggest = (av != AccountVisibility.NONE);
     152           0 :       } catch (IllegalArgumentException err) {
     153           0 :         suggest = cfg.getBoolean("suggest", null, "accounts", true);
     154           7 :       }
     155           7 :       this.suggestConfig = suggest;
     156             :     }
     157           7 :   }
     158             : 
     159             :   @Override
     160             :   public Response<List<AccountInfo>> apply(TopLevelResource rsrc)
     161             :       throws RestApiException, PermissionBackendException {
     162           7 :     if (Strings.isNullOrEmpty(query)) {
     163           1 :       throw new BadRequestException("missing query field");
     164             :     }
     165             : 
     166           6 :     if (suggest && (!suggestConfig || query.length() < suggestFrom)) {
     167           0 :       return Response.ok(Collections.emptyList());
     168             :     }
     169             : 
     170           6 :     Set<FillOptions> fillOptions = EnumSet.of(FillOptions.ID);
     171           6 :     if (options.contains(ListAccountsOption.DETAILS)) {
     172           2 :       fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     173             :     }
     174           6 :     boolean modifyAccountCapabilityChecked = false;
     175           6 :     if (options.contains(ListAccountsOption.ALL_EMAILS)) {
     176           2 :       permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
     177           2 :       modifyAccountCapabilityChecked = true;
     178           2 :       fillOptions.add(FillOptions.EMAIL);
     179           2 :       fillOptions.add(FillOptions.SECONDARY_EMAILS);
     180             :     }
     181           6 :     if (suggest) {
     182           3 :       fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     183           3 :       fillOptions.add(FillOptions.EMAIL);
     184             : 
     185           3 :       if (modifyAccountCapabilityChecked) {
     186           0 :         fillOptions.add(FillOptions.SECONDARY_EMAILS);
     187             :       } else {
     188           3 :         if (permissionBackend.currentUser().test(GlobalPermission.MODIFY_ACCOUNT)) {
     189           3 :           fillOptions.add(FillOptions.SECONDARY_EMAILS);
     190             :         }
     191             :       }
     192             :     }
     193           6 :     accountLoader = accountLoaderFactory.create(fillOptions);
     194             : 
     195           6 :     AccountQueryProcessor queryProcessor = queryProcessorProvider.get();
     196           6 :     if (queryProcessor.isDisabled()) {
     197           0 :       throw new MethodNotAllowedException("query disabled");
     198             :     }
     199             : 
     200           6 :     if (limit != null) {
     201           5 :       queryProcessor.setUserProvidedLimit(limit);
     202             :     }
     203             : 
     204           6 :     if (start != null) {
     205           5 :       if (start < 0) {
     206           2 :         throw new BadRequestException("'start' parameter cannot be less than zero");
     207             :       }
     208           5 :       queryProcessor.setStart(start);
     209             :     }
     210             : 
     211           6 :     Map<Account.Id, AccountInfo> matches = new LinkedHashMap<>();
     212             :     try {
     213             :       Predicate<AccountState> queryPred;
     214           6 :       if (suggest) {
     215           3 :         queryPred = queryBuilder.defaultQuery(query);
     216           3 :         queryProcessor.setUserProvidedLimit(suggestLimit);
     217             :       } else {
     218           6 :         queryPred = queryBuilder.parse(query);
     219             :       }
     220           6 :       if (!AccountPredicates.hasActive(queryPred)) {
     221             :         // if neither 'is:active' nor 'is:inactive' appears in the query only
     222             :         // active accounts should be queried
     223           5 :         queryPred = AccountPredicates.andActive(queryPred);
     224             :       }
     225           6 :       QueryResult<AccountState> result = queryProcessor.query(queryPred);
     226           6 :       for (AccountState accountState : result.entities()) {
     227           5 :         Account.Id id = accountState.account().id();
     228           5 :         matches.put(id, accountLoader.get(id));
     229           5 :       }
     230             : 
     231           6 :       accountLoader.fill();
     232             : 
     233           6 :       List<AccountInfo> sorted =
     234           6 :           AccountInfoComparator.ORDER_NULLS_LAST.sortedCopy(matches.values());
     235           6 :       if (!sorted.isEmpty() && result.more()) {
     236           2 :         sorted.get(sorted.size() - 1)._moreAccounts = true;
     237             :       }
     238           6 :       return Response.ok(sorted);
     239           2 :     } catch (QueryParseException e) {
     240           2 :       if (suggest) {
     241           0 :         return Response.ok(ImmutableList.of());
     242             :       }
     243           2 :       throw new BadRequestException(e.getMessage(), e);
     244             :     }
     245             :   }
     246             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750