Line data Source code
1 : // Copyright (C) 2010 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.httpd;
16 :
17 : import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
18 :
19 : import com.google.common.annotations.VisibleForTesting;
20 : import com.google.common.cache.Cache;
21 : import com.google.common.collect.ImmutableListMultimap;
22 : import com.google.common.collect.ListMultimap;
23 : import com.google.common.collect.Lists;
24 : import com.google.common.flogger.FluentLogger;
25 : import com.google.gerrit.common.Nullable;
26 : import com.google.gerrit.common.data.Capable;
27 : import com.google.gerrit.entities.Project;
28 : import com.google.gerrit.extensions.registration.DynamicSet;
29 : import com.google.gerrit.extensions.restapi.AuthException;
30 : import com.google.gerrit.server.AccessPath;
31 : import com.google.gerrit.server.AnonymousUser;
32 : import com.google.gerrit.server.CurrentUser;
33 : import com.google.gerrit.server.RequestInfo;
34 : import com.google.gerrit.server.RequestListener;
35 : import com.google.gerrit.server.audit.HttpAuditEvent;
36 : import com.google.gerrit.server.cache.CacheModule;
37 : import com.google.gerrit.server.git.GitRepositoryManager;
38 : import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
39 : import com.google.gerrit.server.git.TracingHook;
40 : import com.google.gerrit.server.git.TransferConfig;
41 : import com.google.gerrit.server.git.UploadPackInitializer;
42 : import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
43 : import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
44 : import com.google.gerrit.server.git.validators.UploadValidators;
45 : import com.google.gerrit.server.group.GroupAuditService;
46 : import com.google.gerrit.server.logging.TraceContext;
47 : import com.google.gerrit.server.permissions.PermissionBackend;
48 : import com.google.gerrit.server.permissions.PermissionBackendException;
49 : import com.google.gerrit.server.permissions.ProjectPermission;
50 : import com.google.gerrit.server.plugincontext.PluginSetContext;
51 : import com.google.gerrit.server.project.ProjectCache;
52 : import com.google.gerrit.server.project.ProjectState;
53 : import com.google.gerrit.server.util.time.TimeUtil;
54 : import com.google.inject.AbstractModule;
55 : import com.google.inject.Inject;
56 : import com.google.inject.Provider;
57 : import com.google.inject.Singleton;
58 : import com.google.inject.TypeLiteral;
59 : import com.google.inject.name.Named;
60 : import java.io.IOException;
61 : import java.text.MessageFormat;
62 : import java.time.Duration;
63 : import java.util.Arrays;
64 : import java.util.Collections;
65 : import java.util.HashSet;
66 : import java.util.Map;
67 : import java.util.Set;
68 : import java.util.concurrent.atomic.AtomicLong;
69 : import javax.servlet.Filter;
70 : import javax.servlet.FilterChain;
71 : import javax.servlet.FilterConfig;
72 : import javax.servlet.ServletException;
73 : import javax.servlet.ServletRequest;
74 : import javax.servlet.ServletResponse;
75 : import javax.servlet.http.HttpServletRequest;
76 : import javax.servlet.http.HttpServletResponse;
77 : import javax.servlet.http.HttpServletResponseWrapper;
78 : import org.eclipse.jgit.errors.PackProtocolException;
79 : import org.eclipse.jgit.errors.RepositoryNotFoundException;
80 : import org.eclipse.jgit.http.server.GitServlet;
81 : import org.eclipse.jgit.http.server.GitSmartHttpTools;
82 : import org.eclipse.jgit.http.server.HttpServerText;
83 : import org.eclipse.jgit.http.server.ServletUtils;
84 : import org.eclipse.jgit.http.server.UploadPackErrorHandler;
85 : import org.eclipse.jgit.http.server.resolver.AsIsFileService;
86 : import org.eclipse.jgit.lib.ObjectId;
87 : import org.eclipse.jgit.lib.Repository;
88 : import org.eclipse.jgit.transport.PostUploadHook;
89 : import org.eclipse.jgit.transport.PostUploadHookChain;
90 : import org.eclipse.jgit.transport.PreUploadHook;
91 : import org.eclipse.jgit.transport.PreUploadHookChain;
92 : import org.eclipse.jgit.transport.ReceivePack;
93 : import org.eclipse.jgit.transport.ServiceMayNotContinueException;
94 : import org.eclipse.jgit.transport.UploadPack;
95 : import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
96 : import org.eclipse.jgit.transport.resolver.RepositoryResolver;
97 : import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
98 : import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
99 : import org.eclipse.jgit.transport.resolver.UploadPackFactory;
100 :
101 : /** Serves Git repositories over HTTP. */
102 : @Singleton
103 : public class GitOverHttpServlet extends GitServlet {
104 : private static final long serialVersionUID = 1L;
105 :
106 99 : private static final String ATT_STATE = ProjectState.class.getName();
107 99 : private static final String ATT_ARC = AsyncReceiveCommits.class.getName();
108 : private static final String ID_CACHE = "adv_bases";
109 :
110 : public static final String URL_REGEX;
111 : public static final String GIT_COMMAND_STATUS_HEADER = "X-git-command-status";
112 :
113 8 : private enum GIT_COMMAND_STATUS {
114 8 : OK(0),
115 8 : FAIL(-1);
116 :
117 : private final int exitStatus;
118 :
119 8 : GIT_COMMAND_STATUS(int exitStatus) {
120 8 : this.exitStatus = exitStatus;
121 8 : }
122 :
123 : @Override
124 : public String toString() {
125 8 : return Integer.toString(exitStatus);
126 : }
127 : }
128 :
129 : static {
130 99 : StringBuilder url = new StringBuilder();
131 99 : url.append("^(?:/a)?(?:/p/|/)(.*/(?:info/refs");
132 99 : for (String name : GitSmartHttpTools.VALID_SERVICES) {
133 99 : url.append('|').append(name);
134 99 : }
135 99 : url.append("))$");
136 99 : URL_REGEX = url.toString();
137 99 : }
138 :
139 : static class GitOverHttpServletModule extends AbstractModule {
140 :
141 : private final boolean enableReceive;
142 :
143 99 : GitOverHttpServletModule(boolean enableReceive) {
144 99 : this.enableReceive = enableReceive;
145 99 : }
146 :
147 : @Override
148 : protected void configure() {
149 99 : bind(Resolver.class);
150 99 : bind(UploadFactory.class);
151 99 : bind(UploadFilter.class);
152 99 : bind(new TypeLiteral<ReceivePackFactory<HttpServletRequest>>() {})
153 99 : .to(enableReceive ? ReceiveFactory.class : DisabledReceiveFactory.class);
154 99 : bind(ReceiveFilter.class);
155 99 : install(
156 99 : new CacheModule() {
157 : @Override
158 : protected void configure() {
159 99 : cache(ID_CACHE, AdvertisedObjectsCacheKey.class, new TypeLiteral<Set<ObjectId>>() {})
160 99 : .maximumWeight(4096)
161 99 : .expireAfterWrite(Duration.ofMinutes(10));
162 99 : }
163 : });
164 :
165 : // Don't bind Metrics, which is bound in a parent injector in tests.
166 99 : }
167 : }
168 :
169 : @VisibleForTesting
170 : @Singleton
171 138 : public static class Metrics {
172 : // Recording requests separately in this class is only necessary because of a bug in the
173 : // implementation of the generic RequestMetricsFilter; see
174 : // https://gerrit-review.googlesource.com/c/gerrit/+/211692
175 138 : private final AtomicLong requestsStarted = new AtomicLong();
176 :
177 : void requestStarted() {
178 8 : requestsStarted.incrementAndGet();
179 8 : }
180 :
181 : public long getRequestsStarted() {
182 2 : return requestsStarted.get();
183 : }
184 : }
185 :
186 : static class HttpServletResponseWithStatusWrapper extends HttpServletResponseWrapper {
187 : private int responseStatus;
188 :
189 : HttpServletResponseWithStatusWrapper(HttpServletResponse response) {
190 8 : super(response);
191 : /* Even if we could read the status from response, we assume that it is all
192 : * fine because we entered the filter without any prior issues.
193 : * When Google will have upgraded to Servlet 3.0, we could actually
194 : * call response.getStatus() and the code will be clearer.
195 : */
196 8 : responseStatus = HttpServletResponse.SC_OK;
197 8 : }
198 :
199 : @Override
200 : public void setStatus(int sc) {
201 3 : responseStatus = sc;
202 3 : super.setStatus(sc);
203 3 : }
204 :
205 : @SuppressWarnings("deprecation")
206 : @Override
207 : public void setStatus(int sc, String sm) {
208 0 : responseStatus = sc;
209 0 : super.setStatus(sc, sm);
210 0 : }
211 :
212 : @Override
213 : public void sendError(int sc) throws IOException {
214 0 : this.responseStatus = sc;
215 0 : super.sendError(sc);
216 0 : }
217 :
218 : @Override
219 : public void sendError(int sc, String msg) throws IOException {
220 0 : this.responseStatus = sc;
221 0 : super.sendError(sc, msg);
222 0 : }
223 :
224 : @Override
225 : public void sendRedirect(String location) throws IOException {
226 0 : this.responseStatus = HttpServletResponse.SC_MOVED_TEMPORARILY;
227 0 : super.sendRedirect(location);
228 0 : }
229 :
230 : public int getResponseStatus() {
231 8 : return responseStatus;
232 : }
233 : }
234 :
235 : @Inject
236 : GitOverHttpServlet(
237 : Resolver resolver,
238 : UploadFactory upload,
239 : UploadFilter uploadFilter,
240 : GerritUploadPackErrorHandler uploadPackErrorHandler,
241 : ReceivePackFactory<HttpServletRequest> receive,
242 99 : ReceiveFilter receiveFilter) {
243 99 : setRepositoryResolver(resolver);
244 99 : setAsIsFileService(AsIsFileService.DISABLED);
245 :
246 99 : setUploadPackFactory(upload);
247 99 : setUploadPackErrorHandler(uploadPackErrorHandler);
248 99 : addUploadPackFilter(uploadFilter);
249 :
250 99 : setReceivePackFactory(receive);
251 99 : addReceivePackFilter(receiveFilter);
252 99 : }
253 :
254 : private static String extractWhat(HttpServletRequest request) {
255 8 : StringBuilder commandName = new StringBuilder(request.getRequestURL());
256 8 : if (request.getQueryString() != null) {
257 8 : commandName.append("?").append(request.getQueryString());
258 : }
259 8 : return commandName.toString();
260 : }
261 :
262 : private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
263 8 : if (request.getQueryString() == null) {
264 8 : return ImmutableListMultimap.of();
265 : }
266 : // Explicit cast is required to compile under Servlet API 2.5, where the return type is raw Map.
267 : @SuppressWarnings("cast")
268 8 : Map<String, String[]> parameterMap = (Map<String, String[]>) request.getParameterMap();
269 8 : ImmutableListMultimap.Builder<String, String> b = ImmutableListMultimap.builder();
270 8 : parameterMap.forEach(b::putAll);
271 8 : return b.build();
272 : }
273 :
274 : static class Resolver implements RepositoryResolver<HttpServletRequest> {
275 : private final GitRepositoryManager manager;
276 : private final PermissionBackend permissionBackend;
277 : private final Provider<CurrentUser> userProvider;
278 : private final ProjectCache projectCache;
279 :
280 : @Inject
281 : Resolver(
282 : GitRepositoryManager manager,
283 : PermissionBackend permissionBackend,
284 : Provider<CurrentUser> userProvider,
285 99 : ProjectCache projectCache) {
286 99 : this.manager = manager;
287 99 : this.permissionBackend = permissionBackend;
288 99 : this.userProvider = userProvider;
289 99 : this.projectCache = projectCache;
290 99 : }
291 :
292 : @Override
293 : public Repository open(HttpServletRequest req, String projectName)
294 : throws RepositoryNotFoundException, ServiceNotAuthorizedException,
295 : ServiceNotEnabledException, ServiceMayNotContinueException {
296 8 : while (projectName.endsWith("/")) {
297 0 : projectName = projectName.substring(0, projectName.length() - 1);
298 : }
299 :
300 8 : if (projectName.endsWith(".git")) {
301 : // Be nice and drop the trailing ".git" suffix, which we never keep
302 : // in our database, but clients might mistakenly provide anyway.
303 : //
304 0 : projectName = projectName.substring(0, projectName.length() - 4);
305 0 : while (projectName.endsWith("/")) {
306 0 : projectName = projectName.substring(0, projectName.length() - 1);
307 : }
308 : }
309 :
310 8 : CurrentUser user = userProvider.get();
311 8 : user.setAccessPath(AccessPath.GIT);
312 :
313 : try {
314 8 : Project.NameKey nameKey = Project.nameKey(projectName);
315 8 : ProjectState state =
316 : projectCache
317 8 : .get(nameKey)
318 8 : .orElseThrow(() -> new RepositoryNotFoundException(nameKey.get()));
319 8 : if (!state.statePermitsRead()) {
320 0 : throw new RepositoryNotFoundException(nameKey.get());
321 : }
322 8 : req.setAttribute(ATT_STATE, state);
323 :
324 : try {
325 8 : permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
326 2 : } catch (AuthException e) {
327 2 : if (user instanceof AnonymousUser) {
328 1 : throw new ServiceNotAuthorizedException();
329 : }
330 1 : throw new RepositoryNotFoundException(nameKey.get(), e);
331 8 : }
332 :
333 8 : return manager.openRepository(nameKey);
334 1 : } catch (IOException | PermissionBackendException err) {
335 1 : throw new ServiceMayNotContinueException(projectName + " unavailable", err);
336 : }
337 : }
338 : }
339 :
340 : static class UploadFactory implements UploadPackFactory<HttpServletRequest> {
341 : private final TransferConfig config;
342 : private final DynamicSet<PreUploadHook> preUploadHooks;
343 : private final DynamicSet<PostUploadHook> postUploadHooks;
344 : private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
345 : private final PermissionBackend permissionBackend;
346 :
347 : @Inject
348 : UploadFactory(
349 : TransferConfig tc,
350 : DynamicSet<PreUploadHook> preUploadHooks,
351 : DynamicSet<PostUploadHook> postUploadHooks,
352 : PluginSetContext<UploadPackInitializer> uploadPackInitializers,
353 99 : PermissionBackend permissionBackend) {
354 99 : this.config = tc;
355 99 : this.preUploadHooks = preUploadHooks;
356 99 : this.postUploadHooks = postUploadHooks;
357 99 : this.uploadPackInitializers = uploadPackInitializers;
358 99 : this.permissionBackend = permissionBackend;
359 99 : }
360 :
361 : @Override
362 : public UploadPack create(HttpServletRequest req, Repository repo) {
363 8 : ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
364 8 : UploadPack up =
365 : new UploadPack(
366 8 : PermissionAwareRepositoryManager.wrap(
367 8 : repo, permissionBackend.currentUser().project(state.getNameKey())));
368 8 : up.setPackConfig(config.getPackConfig());
369 8 : up.setTimeout(config.getTimeout());
370 8 : up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks)));
371 8 : up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
372 8 : String header = req.getHeader("Git-Protocol");
373 8 : if (header != null) {
374 8 : String[] params = header.split(":");
375 8 : up.setExtraParameters(Arrays.asList(params));
376 : }
377 8 : uploadPackInitializers.runEach(initializer -> initializer.init(state.getNameKey(), up));
378 8 : return up;
379 : }
380 : }
381 :
382 : static class UploadFilter implements Filter {
383 : private final UploadValidators.Factory uploadValidatorsFactory;
384 : private final PermissionBackend permissionBackend;
385 : private final Provider<CurrentUser> userProvider;
386 : private final GroupAuditService groupAuditService;
387 : private final Metrics metrics;
388 : private final PluginSetContext<RequestListener> requestListeners;
389 : private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
390 : private final Provider<WebSession> sessionProvider;
391 :
392 : @Inject
393 : UploadFilter(
394 : UploadValidators.Factory uploadValidatorsFactory,
395 : PermissionBackend permissionBackend,
396 : Provider<CurrentUser> userProvider,
397 : GroupAuditService groupAuditService,
398 : Metrics metrics,
399 : PluginSetContext<RequestListener> requestListeners,
400 : UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
401 99 : Provider<WebSession> sessionProvider) {
402 99 : this.uploadValidatorsFactory = uploadValidatorsFactory;
403 99 : this.permissionBackend = permissionBackend;
404 99 : this.userProvider = userProvider;
405 99 : this.groupAuditService = groupAuditService;
406 99 : this.metrics = metrics;
407 99 : this.requestListeners = requestListeners;
408 99 : this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
409 99 : this.sessionProvider = sessionProvider;
410 99 : }
411 :
412 : @Override
413 : public void doFilter(ServletRequest request, ServletResponse response, FilterChain next)
414 : throws IOException, ServletException {
415 8 : metrics.requestStarted();
416 : // The Resolver above already checked READ access for us.
417 8 : Repository repo = ServletUtils.getRepository(request);
418 8 : ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
419 8 : UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER);
420 8 : PermissionBackend.ForProject perm =
421 8 : permissionBackend.currentUser().project(state.getNameKey());
422 8 : HttpServletResponseWithStatusWrapper responseWrapper =
423 : new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
424 8 : HttpServletRequest httpRequest = (HttpServletRequest) request;
425 8 : String sessionId = getSessionIdOrNull(sessionProvider);
426 :
427 8 : try (TraceContext traceContext = TraceContext.open()) {
428 8 : RequestInfo requestInfo =
429 8 : RequestInfo.builder(
430 8 : RequestInfo.RequestType.GIT_UPLOAD, userProvider.get(), traceContext)
431 8 : .project(state.getNameKey())
432 8 : .build();
433 8 : requestListeners.runEach(l -> l.onRequest(requestInfo));
434 :
435 : try {
436 8 : if (!perm.test(ProjectPermission.RUN_UPLOAD_PACK)) {
437 0 : GitSmartHttpTools.sendError(
438 : (HttpServletRequest) request,
439 : responseWrapper,
440 : HttpServletResponse.SC_FORBIDDEN,
441 : "upload-pack not permitted on this server");
442 0 : return;
443 : }
444 0 : } catch (PermissionBackendException e) {
445 0 : responseWrapper.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
446 0 : throw new ServletException(e);
447 8 : }
448 :
449 : // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
450 : // may have been overridden by a proxy server -- we'll try to avoid this.
451 8 : UploadValidators uploadValidators =
452 8 : uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
453 8 : up.setPreUploadHook(
454 8 : PreUploadHookChain.newChain(
455 8 : Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
456 8 : if (state.isAllUsers()) {
457 1 : up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
458 : }
459 :
460 8 : try (TracingHook tracingHook = new TracingHook()) {
461 8 : up.setProtocolV2Hook(tracingHook);
462 8 : next.doFilter(httpRequest, responseWrapper);
463 : }
464 0 : } finally {
465 8 : groupAuditService.dispatch(
466 : new HttpAuditEvent(
467 : sessionId,
468 8 : userProvider.get(),
469 8 : extractWhat(httpRequest),
470 8 : TimeUtil.nowMs(),
471 8 : extractParameters(httpRequest),
472 8 : httpRequest.getMethod(),
473 : httpRequest,
474 8 : responseWrapper.getResponseStatus(),
475 : responseWrapper));
476 : }
477 8 : }
478 :
479 : @Override
480 8 : public void init(FilterConfig config) {}
481 :
482 : @Override
483 8 : public void destroy() {}
484 : }
485 :
486 99 : static class GerritUploadPackErrorHandler implements UploadPackErrorHandler {
487 99 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
488 :
489 : @Override
490 : public void upload(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r)
491 : throws IOException {
492 8 : rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.FAIL.toString());
493 : try {
494 8 : r.upload();
495 8 : rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.OK.toString());
496 0 : } catch (ServiceMayNotContinueException e) {
497 0 : if (!e.isOutput() && !rsp.isCommitted()) {
498 0 : rsp.reset();
499 0 : sendError(req, rsp, e.getStatusCode(), e.getMessage());
500 : }
501 3 : } catch (Throwable e) {
502 3 : logger.atSevere().withCause(e).log(
503 : "%s",
504 3 : MessageFormat.format(
505 3 : HttpServerText.get().internalErrorDuringUploadPack,
506 3 : ServletUtils.getRepository(req)));
507 3 : if (!rsp.isCommitted()) {
508 3 : rsp.reset();
509 3 : String msg = e instanceof PackProtocolException ? e.getMessage() : null;
510 3 : sendError(req, rsp, UploadPackErrorHandler.statusCodeForThrowable(e), msg);
511 : }
512 8 : }
513 8 : }
514 : }
515 :
516 : static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
517 : private final AsyncReceiveCommits.Factory factory;
518 : private final Provider<CurrentUser> userProvider;
519 :
520 : @Inject
521 99 : ReceiveFactory(AsyncReceiveCommits.Factory factory, Provider<CurrentUser> userProvider) {
522 99 : this.factory = factory;
523 99 : this.userProvider = userProvider;
524 99 : }
525 :
526 : @Override
527 : public ReceivePack create(HttpServletRequest req, Repository db)
528 : throws ServiceNotAuthorizedException {
529 7 : final ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
530 :
531 7 : if (!userProvider.get().isIdentifiedUser()) {
532 : // Anonymous users are not permitted to push.
533 0 : throw new ServiceNotAuthorizedException();
534 : }
535 :
536 7 : AsyncReceiveCommits arc =
537 7 : factory.create(state, userProvider.get().asIdentifiedUser(), db, null);
538 7 : ReceivePack rp = arc.getReceivePack();
539 7 : req.setAttribute(ATT_ARC, arc);
540 7 : return rp;
541 : }
542 : }
543 :
544 1 : static class DisabledReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
545 : @Override
546 : public ReceivePack create(HttpServletRequest req, Repository db)
547 : throws ServiceNotEnabledException {
548 0 : throw new ServiceNotEnabledException();
549 : }
550 : }
551 :
552 : static class ReceiveFilter implements Filter {
553 : private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
554 : private final PermissionBackend permissionBackend;
555 : private final Provider<CurrentUser> userProvider;
556 : private final GroupAuditService groupAuditService;
557 : private final Metrics metrics;
558 : private final Provider<WebSession> sessionProvider;
559 :
560 : @Inject
561 : ReceiveFilter(
562 : @Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
563 : PermissionBackend permissionBackend,
564 : Provider<CurrentUser> userProvider,
565 : GroupAuditService groupAuditService,
566 : Metrics metrics,
567 99 : Provider<WebSession> sessionProvider) {
568 99 : this.cache = cache;
569 99 : this.permissionBackend = permissionBackend;
570 99 : this.userProvider = userProvider;
571 99 : this.groupAuditService = groupAuditService;
572 99 : this.metrics = metrics;
573 99 : this.sessionProvider = sessionProvider;
574 99 : }
575 :
576 : @Override
577 : public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
578 : throws IOException, ServletException {
579 7 : metrics.requestStarted();
580 7 : boolean isGet = "GET".equalsIgnoreCase(((HttpServletRequest) request).getMethod());
581 :
582 7 : AsyncReceiveCommits arc = (AsyncReceiveCommits) request.getAttribute(ATT_ARC);
583 :
584 : // Send refs down the wire.
585 7 : ReceivePack rp = arc.getReceivePack();
586 7 : rp.getAdvertiseRefsHook().advertiseRefs(rp);
587 :
588 7 : ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
589 7 : HttpServletResponseWithStatusWrapper responseWrapper =
590 : new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
591 7 : HttpServletRequest httpRequest = (HttpServletRequest) request;
592 : Capable canUpload;
593 : try {
594 : try {
595 7 : if (!permissionBackend
596 7 : .currentUser()
597 7 : .project(state.getNameKey())
598 7 : .test(ProjectPermission.RUN_RECEIVE_PACK)) {
599 0 : GitSmartHttpTools.sendError(
600 : httpRequest,
601 : responseWrapper,
602 : HttpServletResponse.SC_FORBIDDEN,
603 : "receive-pack not permitted on this server");
604 0 : return;
605 : }
606 7 : canUpload = arc.canUpload();
607 0 : } catch (PermissionBackendException e) {
608 0 : throw new RuntimeException(e);
609 7 : }
610 : } finally {
611 7 : groupAuditService.dispatch(
612 : new HttpAuditEvent(
613 7 : getSessionIdOrNull(sessionProvider),
614 7 : userProvider.get(),
615 7 : extractWhat(httpRequest),
616 7 : TimeUtil.nowMs(),
617 7 : extractParameters(httpRequest),
618 7 : httpRequest.getMethod(),
619 : httpRequest,
620 7 : responseWrapper.getResponseStatus(),
621 : responseWrapper));
622 : }
623 :
624 7 : if (canUpload != Capable.OK) {
625 0 : GitSmartHttpTools.sendError(
626 : httpRequest,
627 : responseWrapper,
628 : HttpServletResponse.SC_FORBIDDEN,
629 0 : "\n" + canUpload.getMessage());
630 0 : return;
631 : }
632 :
633 7 : if (!rp.isCheckReferencedObjectsAreReachable()) {
634 6 : chain.doFilter(request, responseWrapper);
635 6 : return;
636 : }
637 :
638 5 : if (!userProvider.get().isIdentifiedUser()) {
639 0 : chain.doFilter(request, responseWrapper);
640 0 : return;
641 : }
642 :
643 5 : AdvertisedObjectsCacheKey cacheKey =
644 5 : AdvertisedObjectsCacheKey.create(userProvider.get().getAccountId(), state.getNameKey());
645 :
646 5 : if (isGet) {
647 5 : cache.invalidate(cacheKey);
648 : } else {
649 5 : Set<ObjectId> ids = cache.getIfPresent(cacheKey);
650 5 : if (ids != null) {
651 5 : rp.getAdvertisedObjects().addAll(ids);
652 5 : cache.invalidate(cacheKey);
653 : }
654 : }
655 :
656 5 : chain.doFilter(request, responseWrapper);
657 :
658 5 : if (isGet) {
659 5 : cache.put(cacheKey, Collections.unmodifiableSet(new HashSet<>(rp.getAdvertisedObjects())));
660 : }
661 5 : }
662 :
663 : @Override
664 8 : public void init(FilterConfig arg0) {}
665 :
666 : @Override
667 8 : public void destroy() {}
668 : }
669 :
670 : @Nullable
671 : private static String getSessionIdOrNull(Provider<WebSession> sessionProvider) {
672 8 : WebSession session = sessionProvider.get();
673 8 : if (session.isSignedIn()) {
674 8 : return session.getSessionId();
675 : }
676 3 : return null;
677 : }
678 : }
|