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 : }
|