LCOV - code coverage report
Current view: top level - server - RequestConfig.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 65 69 94.2 %
Date: 2022-11-19 15:00:39 Functions: 17 17 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2021 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;
      16             : 
      17             : import com.google.auto.value.AutoValue;
      18             : import com.google.common.collect.ImmutableList;
      19             : import com.google.common.collect.ImmutableSet;
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.entities.Account;
      22             : import java.util.Optional;
      23             : import java.util.regex.Pattern;
      24             : import java.util.regex.PatternSyntaxException;
      25             : import org.eclipse.jgit.errors.ConfigInvalidException;
      26             : import org.eclipse.jgit.lib.Config;
      27             : 
      28             : /**
      29             :  * Represents a configuration on request level that matches requests by request type, URI pattern,
      30             :  * caller and/or project pattern.
      31             :  */
      32             : @AutoValue
      33           2 : public abstract class RequestConfig {
      34         138 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      35             : 
      36             :   public static ImmutableList<RequestConfig> parseConfigs(Config cfg, String section) {
      37         138 :     ImmutableList.Builder<RequestConfig> requestConfigs = ImmutableList.builder();
      38             : 
      39         138 :     for (String id : cfg.getSubsections(section)) {
      40             :       try {
      41           2 :         RequestConfig.Builder requestConfig = RequestConfig.builder(cfg, section, id);
      42           2 :         requestConfig.requestTypes(parseRequestTypes(cfg, section, id));
      43           2 :         requestConfig.requestUriPatterns(parseRequestUriPatterns(cfg, section, id));
      44           2 :         requestConfig.excludedRequestUriPatterns(parseExcludedRequestUriPatterns(cfg, section, id));
      45           2 :         requestConfig.accountIds(parseAccounts(cfg, section, id));
      46           2 :         requestConfig.projectPatterns(parseProjectPatterns(cfg, section, id));
      47           2 :         requestConfigs.add(requestConfig.build());
      48           1 :       } catch (ConfigInvalidException e) {
      49           1 :         logger.atWarning().log("Ignoring invalid %s configuration:\n %s", section, e.getMessage());
      50           2 :       }
      51           2 :     }
      52             : 
      53         138 :     return requestConfigs.build();
      54             :   }
      55             : 
      56             :   private static ImmutableSet<String> parseRequestTypes(Config cfg, String section, String id) {
      57           2 :     return ImmutableSet.copyOf(cfg.getStringList(section, id, "requestType"));
      58             :   }
      59             : 
      60             :   private static ImmutableSet<Pattern> parseRequestUriPatterns(
      61             :       Config cfg, String section, String id) throws ConfigInvalidException {
      62           2 :     return parsePatterns(cfg, section, id, "requestUriPattern");
      63             :   }
      64             : 
      65             :   private static ImmutableSet<Pattern> parseExcludedRequestUriPatterns(
      66             :       Config cfg, String section, String id) throws ConfigInvalidException {
      67           2 :     return parsePatterns(cfg, section, id, "excludedRequestUriPattern");
      68             :   }
      69             : 
      70             :   private static ImmutableSet<Account.Id> parseAccounts(Config cfg, String section, String id)
      71             :       throws ConfigInvalidException {
      72           2 :     ImmutableSet.Builder<Account.Id> accountIds = ImmutableSet.builder();
      73           2 :     String[] accounts = cfg.getStringList(section, id, "account");
      74           2 :     for (String account : accounts) {
      75           1 :       Optional<Account.Id> accountId = Account.Id.tryParse(account);
      76           1 :       if (!accountId.isPresent()) {
      77           1 :         throw new ConfigInvalidException(
      78           1 :             String.format(
      79             :                 "Invalid request config ('%s.%s.account = %s'): invalid account ID",
      80             :                 section, id, account));
      81             :       }
      82           1 :       accountIds.add(accountId.get());
      83             :     }
      84           2 :     return accountIds.build();
      85             :   }
      86             : 
      87             :   private static ImmutableSet<Pattern> parseProjectPatterns(Config cfg, String section, String id)
      88             :       throws ConfigInvalidException {
      89           2 :     return parsePatterns(cfg, section, id, "projectPattern");
      90             :   }
      91             : 
      92             :   private static ImmutableSet<Pattern> parsePatterns(
      93             :       Config cfg, String section, String id, String name) throws ConfigInvalidException {
      94           2 :     ImmutableSet.Builder<Pattern> patterns = ImmutableSet.builder();
      95           2 :     String[] patternRegExs = cfg.getStringList(section, id, name);
      96           2 :     for (String patternRegEx : patternRegExs) {
      97             :       try {
      98           1 :         patterns.add(Pattern.compile(patternRegEx));
      99           1 :       } catch (PatternSyntaxException e) {
     100           1 :         throw new ConfigInvalidException(
     101           1 :             String.format(
     102             :                 "Invalid request config ('%s.%s.%s = %s'): %s",
     103           1 :                 section, id, name, patternRegEx, e.getMessage()));
     104           1 :       }
     105             :     }
     106           2 :     return patterns.build();
     107             :   }
     108             : 
     109             :   /** the config from which this request config was read */
     110             :   abstract Config cfg();
     111             : 
     112             :   /** the section from which this request config was read */
     113             :   abstract String section();
     114             : 
     115             :   /** ID of the config, also the subsection from which this request config was read */
     116             :   abstract String id();
     117             : 
     118             :   /** request types that should be matched */
     119             :   abstract ImmutableSet<String> requestTypes();
     120             : 
     121             :   /** pattern matching request URIs */
     122             :   abstract ImmutableSet<Pattern> requestUriPatterns();
     123             : 
     124             :   /** pattern matching request URIs to be excluded */
     125             :   abstract ImmutableSet<Pattern> excludedRequestUriPatterns();
     126             : 
     127             :   /** accounts IDs matching calling user */
     128             :   abstract ImmutableSet<Account.Id> accountIds();
     129             : 
     130             :   /** pattern matching projects names */
     131             :   abstract ImmutableSet<Pattern> projectPatterns();
     132             : 
     133             :   private static Builder builder(Config cfg, String section, String id) {
     134           2 :     return new AutoValue_RequestConfig.Builder().cfg(cfg).section(section).id(id);
     135             :   }
     136             : 
     137             :   /**
     138             :    * Whether this request config matches a given request.
     139             :    *
     140             :    * @param requestInfo request info
     141             :    * @return whether this request config matches
     142             :    */
     143             :   boolean matches(RequestInfo requestInfo) {
     144             :     // If in the request config request types are set and none of them matches, then the request is
     145             :     // not matched.
     146           2 :     if (!requestTypes().isEmpty()
     147           1 :         && requestTypes().stream()
     148           1 :             .noneMatch(type -> type.equalsIgnoreCase(requestInfo.requestType()))) {
     149           1 :       return false;
     150             :     }
     151             : 
     152             :     // If in the request config request URI patterns are set and none of them matches, then the
     153             :     // request is not matched.
     154           2 :     if (!requestUriPatterns().isEmpty()) {
     155           1 :       if (!requestInfo.requestUri().isPresent()) {
     156             :         // The request has no request URI, hence it cannot match a request URI pattern.
     157           0 :         return false;
     158             :       }
     159             : 
     160           1 :       if (requestUriPatterns().stream()
     161           1 :           .noneMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) {
     162           1 :         return false;
     163             :       }
     164             :     }
     165             : 
     166             :     // If the request URI matches an excluded request URI pattern, then the request is not matched.
     167           2 :     if (requestInfo.requestUri().isPresent()
     168           1 :         && excludedRequestUriPatterns().stream()
     169           1 :             .anyMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) {
     170           1 :       return false;
     171             :     }
     172             : 
     173             :     // If in the request config accounts are set and none of them matches, then the request is not
     174             :     // matched.
     175           2 :     if (!accountIds().isEmpty()) {
     176             :       try {
     177           1 :         if (accountIds().stream()
     178           1 :             .noneMatch(id -> id.equals(requestInfo.callingUser().getAccountId()))) {
     179           1 :           return false;
     180             :         }
     181           0 :       } catch (UnsupportedOperationException e) {
     182             :         // The calling user is not logged in, hence it cannot match an account.
     183           0 :         return false;
     184           1 :       }
     185             :     }
     186             : 
     187             :     // If in the request config project patterns are set and none of them matches, then the request
     188             :     // is not matched.
     189           2 :     if (!projectPatterns().isEmpty()) {
     190           1 :       if (!requestInfo.project().isPresent()) {
     191             :         // The request is not for a project, hence it cannot match a project pattern.
     192           0 :         return false;
     193             :       }
     194             : 
     195           1 :       if (projectPatterns().stream()
     196           1 :           .noneMatch(p -> p.matcher(requestInfo.project().get().get()).matches())) {
     197           1 :         return false;
     198             :       }
     199             :     }
     200             : 
     201             :     // For any match criteria (request type, request URI pattern, account, project pattern) that
     202             :     // was specified in the request config, at least one of the configured value matched the
     203             :     // request.
     204           2 :     return true;
     205             :   }
     206             : 
     207             :   @AutoValue.Builder
     208           2 :   abstract static class Builder {
     209             :     abstract Builder cfg(Config cfg);
     210             : 
     211             :     abstract Builder section(String section);
     212             : 
     213             :     abstract Builder id(String id);
     214             : 
     215             :     abstract Builder requestTypes(ImmutableSet<String> requestTypes);
     216             : 
     217             :     abstract Builder requestUriPatterns(ImmutableSet<Pattern> requestUriPatterns);
     218             : 
     219             :     abstract Builder excludedRequestUriPatterns(ImmutableSet<Pattern> excludedRequestUriPatterns);
     220             : 
     221             :     abstract Builder accountIds(ImmutableSet<Account.Id> accountIds);
     222             : 
     223             :     abstract Builder projectPatterns(ImmutableSet<Pattern> projectPatterns);
     224             : 
     225             :     abstract RequestConfig build();
     226             :   }
     227             : }

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