| // Copyright (C) 2021 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.entities.Account; |
| import java.util.Optional; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.Config; |
| |
| /** |
| * Represents a configuration on request level that matches requests by request type, URI pattern, |
| * caller and/or project pattern. |
| */ |
| @AutoValue |
| public abstract class RequestConfig { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| public static ImmutableList<RequestConfig> parseConfigs(Config cfg, String section) { |
| ImmutableList.Builder<RequestConfig> requestConfigs = ImmutableList.builder(); |
| |
| for (String id : cfg.getSubsections(section)) { |
| try { |
| RequestConfig.Builder requestConfig = RequestConfig.builder(cfg, section, id); |
| requestConfig.requestTypes(parseRequestTypes(cfg, section, id)); |
| requestConfig.requestUriPatterns(parseRequestUriPatterns(cfg, section, id)); |
| requestConfig.excludedRequestUriPatterns(parseExcludedRequestUriPatterns(cfg, section, id)); |
| requestConfig.accountIds(parseAccounts(cfg, section, id)); |
| requestConfig.projectPatterns(parseProjectPatterns(cfg, section, id)); |
| requestConfigs.add(requestConfig.build()); |
| } catch (ConfigInvalidException e) { |
| logger.atWarning().log("Ignoring invalid %s configuration:\n %s", section, e.getMessage()); |
| } |
| } |
| |
| return requestConfigs.build(); |
| } |
| |
| private static ImmutableSet<String> parseRequestTypes(Config cfg, String section, String id) { |
| return ImmutableSet.copyOf(cfg.getStringList(section, id, "requestType")); |
| } |
| |
| private static ImmutableSet<Pattern> parseRequestUriPatterns( |
| Config cfg, String section, String id) throws ConfigInvalidException { |
| return parsePatterns(cfg, section, id, "requestUriPattern"); |
| } |
| |
| private static ImmutableSet<Pattern> parseExcludedRequestUriPatterns( |
| Config cfg, String section, String id) throws ConfigInvalidException { |
| return parsePatterns(cfg, section, id, "excludedRequestUriPattern"); |
| } |
| |
| private static ImmutableSet<Account.Id> parseAccounts(Config cfg, String section, String id) |
| throws ConfigInvalidException { |
| ImmutableSet.Builder<Account.Id> accountIds = ImmutableSet.builder(); |
| String[] accounts = cfg.getStringList(section, id, "account"); |
| for (String account : accounts) { |
| Optional<Account.Id> accountId = Account.Id.tryParse(account); |
| if (!accountId.isPresent()) { |
| throw new ConfigInvalidException( |
| String.format( |
| "Invalid request config ('%s.%s.account = %s'): invalid account ID", |
| section, id, account)); |
| } |
| accountIds.add(accountId.get()); |
| } |
| return accountIds.build(); |
| } |
| |
| private static ImmutableSet<Pattern> parseProjectPatterns(Config cfg, String section, String id) |
| throws ConfigInvalidException { |
| return parsePatterns(cfg, section, id, "projectPattern"); |
| } |
| |
| private static ImmutableSet<Pattern> parsePatterns( |
| Config cfg, String section, String id, String name) throws ConfigInvalidException { |
| ImmutableSet.Builder<Pattern> patterns = ImmutableSet.builder(); |
| String[] patternRegExs = cfg.getStringList(section, id, name); |
| for (String patternRegEx : patternRegExs) { |
| try { |
| patterns.add(Pattern.compile(patternRegEx)); |
| } catch (PatternSyntaxException e) { |
| throw new ConfigInvalidException( |
| String.format( |
| "Invalid request config ('%s.%s.%s = %s'): %s", |
| section, id, name, patternRegEx, e.getMessage())); |
| } |
| } |
| return patterns.build(); |
| } |
| |
| /** the config from which this request config was read */ |
| abstract Config cfg(); |
| |
| /** the section from which this request config was read */ |
| abstract String section(); |
| |
| /** ID of the config, also the subsection from which this request config was read */ |
| abstract String id(); |
| |
| /** request types that should be matched */ |
| abstract ImmutableSet<String> requestTypes(); |
| |
| /** pattern matching request URIs */ |
| abstract ImmutableSet<Pattern> requestUriPatterns(); |
| |
| /** pattern matching request URIs to be excluded */ |
| abstract ImmutableSet<Pattern> excludedRequestUriPatterns(); |
| |
| /** accounts IDs matching calling user */ |
| abstract ImmutableSet<Account.Id> accountIds(); |
| |
| /** pattern matching projects names */ |
| abstract ImmutableSet<Pattern> projectPatterns(); |
| |
| private static Builder builder(Config cfg, String section, String id) { |
| return new AutoValue_RequestConfig.Builder().cfg(cfg).section(section).id(id); |
| } |
| |
| /** |
| * Whether this request config matches a given request. |
| * |
| * @param requestInfo request info |
| * @return whether this request config matches |
| */ |
| boolean matches(RequestInfo requestInfo) { |
| // If in the request config request types are set and none of them matches, then the request is |
| // not matched. |
| if (!requestTypes().isEmpty() |
| && requestTypes().stream() |
| .noneMatch(type -> type.equalsIgnoreCase(requestInfo.requestType()))) { |
| return false; |
| } |
| |
| // If in the request config request URI patterns are set and none of them matches, then the |
| // request is not matched. |
| if (!requestUriPatterns().isEmpty()) { |
| if (!requestInfo.requestUri().isPresent()) { |
| // The request has no request URI, hence it cannot match a request URI pattern. |
| return false; |
| } |
| |
| if (requestUriPatterns().stream() |
| .noneMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) { |
| return false; |
| } |
| } |
| |
| // If the request URI matches an excluded request URI pattern, then the request is not matched. |
| if (requestInfo.requestUri().isPresent() |
| && excludedRequestUriPatterns().stream() |
| .anyMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) { |
| return false; |
| } |
| |
| // If in the request config accounts are set and none of them matches, then the request is not |
| // matched. |
| if (!accountIds().isEmpty()) { |
| try { |
| if (accountIds().stream() |
| .noneMatch(id -> id.equals(requestInfo.callingUser().getAccountId()))) { |
| return false; |
| } |
| } catch (UnsupportedOperationException e) { |
| // The calling user is not logged in, hence it cannot match an account. |
| return false; |
| } |
| } |
| |
| // If in the request config project patterns are set and none of them matches, then the request |
| // is not matched. |
| if (!projectPatterns().isEmpty()) { |
| if (!requestInfo.project().isPresent()) { |
| // The request is not for a project, hence it cannot match a project pattern. |
| return false; |
| } |
| |
| if (projectPatterns().stream() |
| .noneMatch(p -> p.matcher(requestInfo.project().get().get()).matches())) { |
| return false; |
| } |
| } |
| |
| // For any match criteria (request type, request URI pattern, account, project pattern) that |
| // was specified in the request config, at least one of the configured value matched the |
| // request. |
| return true; |
| } |
| |
| @AutoValue.Builder |
| abstract static class Builder { |
| abstract Builder cfg(Config cfg); |
| |
| abstract Builder section(String section); |
| |
| abstract Builder id(String id); |
| |
| abstract Builder requestTypes(ImmutableSet<String> requestTypes); |
| |
| abstract Builder requestUriPatterns(ImmutableSet<Pattern> requestUriPatterns); |
| |
| abstract Builder excludedRequestUriPatterns(ImmutableSet<Pattern> excludedRequestUriPatterns); |
| |
| abstract Builder accountIds(ImmutableSet<Account.Id> accountIds); |
| |
| abstract Builder projectPatterns(ImmutableSet<Pattern> projectPatterns); |
| |
| abstract RequestConfig build(); |
| } |
| } |