| // Copyright (C) 2010 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.httpd; | |
| import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.cache.Cache; | |
| import com.google.common.collect.ImmutableListMultimap; | |
| import com.google.common.collect.ListMultimap; | |
| import com.google.common.collect.Lists; | |
| import com.google.common.flogger.FluentLogger; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.common.data.Capable; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.extensions.registration.DynamicSet; | |
| import com.google.gerrit.extensions.restapi.AuthException; | |
| import com.google.gerrit.server.AccessPath; | |
| import com.google.gerrit.server.AnonymousUser; | |
| import com.google.gerrit.server.CurrentUser; | |
| import com.google.gerrit.server.RequestInfo; | |
| import com.google.gerrit.server.RequestListener; | |
| import com.google.gerrit.server.audit.HttpAuditEvent; | |
| import com.google.gerrit.server.cache.CacheModule; | |
| import com.google.gerrit.server.git.GitRepositoryManager; | |
| import com.google.gerrit.server.git.PermissionAwareRepositoryManager; | |
| import com.google.gerrit.server.git.TracingHook; | |
| import com.google.gerrit.server.git.TransferConfig; | |
| import com.google.gerrit.server.git.UploadPackInitializer; | |
| import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook; | |
| import com.google.gerrit.server.git.receive.AsyncReceiveCommits; | |
| import com.google.gerrit.server.git.validators.UploadValidators; | |
| import com.google.gerrit.server.group.GroupAuditService; | |
| import com.google.gerrit.server.logging.TraceContext; | |
| import com.google.gerrit.server.permissions.PermissionBackend; | |
| import com.google.gerrit.server.permissions.PermissionBackendException; | |
| import com.google.gerrit.server.permissions.ProjectPermission; | |
| import com.google.gerrit.server.plugincontext.PluginSetContext; | |
| import com.google.gerrit.server.project.ProjectCache; | |
| import com.google.gerrit.server.project.ProjectState; | |
| import com.google.gerrit.server.util.time.TimeUtil; | |
| import com.google.inject.AbstractModule; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Provider; | |
| import com.google.inject.Singleton; | |
| import com.google.inject.TypeLiteral; | |
| import com.google.inject.name.Named; | |
| import java.io.IOException; | |
| import java.text.MessageFormat; | |
| import java.time.Duration; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.concurrent.atomic.AtomicLong; | |
| import javax.servlet.Filter; | |
| import javax.servlet.FilterChain; | |
| import javax.servlet.FilterConfig; | |
| import javax.servlet.ServletException; | |
| import javax.servlet.ServletRequest; | |
| import javax.servlet.ServletResponse; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import javax.servlet.http.HttpServletResponseWrapper; | |
| import org.eclipse.jgit.errors.PackProtocolException; | |
| import org.eclipse.jgit.errors.RepositoryNotFoundException; | |
| import org.eclipse.jgit.http.server.GitServlet; | |
| import org.eclipse.jgit.http.server.GitSmartHttpTools; | |
| import org.eclipse.jgit.http.server.HttpServerText; | |
| import org.eclipse.jgit.http.server.ServletUtils; | |
| import org.eclipse.jgit.http.server.UploadPackErrorHandler; | |
| import org.eclipse.jgit.http.server.resolver.AsIsFileService; | |
| import org.eclipse.jgit.lib.ObjectId; | |
| import org.eclipse.jgit.lib.Repository; | |
| import org.eclipse.jgit.transport.PostUploadHook; | |
| import org.eclipse.jgit.transport.PostUploadHookChain; | |
| import org.eclipse.jgit.transport.PreUploadHook; | |
| import org.eclipse.jgit.transport.PreUploadHookChain; | |
| import org.eclipse.jgit.transport.ReceivePack; | |
| import org.eclipse.jgit.transport.ServiceMayNotContinueException; | |
| import org.eclipse.jgit.transport.UploadPack; | |
| import org.eclipse.jgit.transport.resolver.ReceivePackFactory; | |
| import org.eclipse.jgit.transport.resolver.RepositoryResolver; | |
| import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; | |
| import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; | |
| import org.eclipse.jgit.transport.resolver.UploadPackFactory; | |
| /** Serves Git repositories over HTTP. */ | |
| @Singleton | |
| public class GitOverHttpServlet extends GitServlet { | |
| private static final long serialVersionUID = 1L; | |
| private static final String ATT_STATE = ProjectState.class.getName(); | |
| private static final String ATT_ARC = AsyncReceiveCommits.class.getName(); | |
| private static final String ID_CACHE = "adv_bases"; | |
| public static final String URL_REGEX; | |
| public static final String GIT_COMMAND_STATUS_HEADER = "X-git-command-status"; | |
| private enum GIT_COMMAND_STATUS { | |
| OK(0), | |
| FAIL(-1); | |
| private final int exitStatus; | |
| GIT_COMMAND_STATUS(int exitStatus) { | |
| this.exitStatus = exitStatus; | |
| } | |
| @Override | |
| public String toString() { | |
| return Integer.toString(exitStatus); | |
| } | |
| } | |
| static { | |
| StringBuilder url = new StringBuilder(); | |
| url.append("^(?:/a)?(?:/p/|/)(.*/(?:info/refs"); | |
| for (String name : GitSmartHttpTools.VALID_SERVICES) { | |
| url.append('|').append(name); | |
| } | |
| url.append("))$"); | |
| URL_REGEX = url.toString(); | |
| } | |
| static class GitOverHttpServletModule extends AbstractModule { | |
| private final boolean enableReceive; | |
| GitOverHttpServletModule(boolean enableReceive) { | |
| this.enableReceive = enableReceive; | |
| } | |
| @Override | |
| protected void configure() { | |
| bind(Resolver.class); | |
| bind(UploadFactory.class); | |
| bind(UploadFilter.class); | |
| bind(new TypeLiteral<ReceivePackFactory<HttpServletRequest>>() {}) | |
| .to(enableReceive ? ReceiveFactory.class : DisabledReceiveFactory.class); | |
| bind(ReceiveFilter.class); | |
| install( | |
| new CacheModule() { | |
| @Override | |
| protected void configure() { | |
| cache(ID_CACHE, AdvertisedObjectsCacheKey.class, new TypeLiteral<Set<ObjectId>>() {}) | |
| .maximumWeight(4096) | |
| .expireAfterWrite(Duration.ofMinutes(10)); | |
| } | |
| }); | |
| // Don't bind Metrics, which is bound in a parent injector in tests. | |
| } | |
| } | |
| @VisibleForTesting | |
| @Singleton | |
| public static class Metrics { | |
| // Recording requests separately in this class is only necessary because of a bug in the | |
| // implementation of the generic RequestMetricsFilter; see | |
| // https://gerrit-review.googlesource.com/c/gerrit/+/211692 | |
| private final AtomicLong requestsStarted = new AtomicLong(); | |
| void requestStarted() { | |
| requestsStarted.incrementAndGet(); | |
| } | |
| public long getRequestsStarted() { | |
| return requestsStarted.get(); | |
| } | |
| } | |
| static class HttpServletResponseWithStatusWrapper extends HttpServletResponseWrapper { | |
| private int responseStatus; | |
| HttpServletResponseWithStatusWrapper(HttpServletResponse response) { | |
| super(response); | |
| /* Even if we could read the status from response, we assume that it is all | |
| * fine because we entered the filter without any prior issues. | |
| * When Google will have upgraded to Servlet 3.0, we could actually | |
| * call response.getStatus() and the code will be clearer. | |
| */ | |
| responseStatus = HttpServletResponse.SC_OK; | |
| } | |
| @Override | |
| public void setStatus(int sc) { | |
| responseStatus = sc; | |
| super.setStatus(sc); | |
| } | |
| @SuppressWarnings("deprecation") | |
| @Override | |
| public void setStatus(int sc, String sm) { | |
| responseStatus = sc; | |
| super.setStatus(sc, sm); | |
| } | |
| @Override | |
| public void sendError(int sc) throws IOException { | |
| this.responseStatus = sc; | |
| super.sendError(sc); | |
| } | |
| @Override | |
| public void sendError(int sc, String msg) throws IOException { | |
| this.responseStatus = sc; | |
| super.sendError(sc, msg); | |
| } | |
| @Override | |
| public void sendRedirect(String location) throws IOException { | |
| this.responseStatus = HttpServletResponse.SC_MOVED_TEMPORARILY; | |
| super.sendRedirect(location); | |
| } | |
| public int getResponseStatus() { | |
| return responseStatus; | |
| } | |
| } | |
| @Inject | |
| GitOverHttpServlet( | |
| Resolver resolver, | |
| UploadFactory upload, | |
| UploadFilter uploadFilter, | |
| GerritUploadPackErrorHandler uploadPackErrorHandler, | |
| ReceivePackFactory<HttpServletRequest> receive, | |
| ReceiveFilter receiveFilter) { | |
| setRepositoryResolver(resolver); | |
| setAsIsFileService(AsIsFileService.DISABLED); | |
| setUploadPackFactory(upload); | |
| setUploadPackErrorHandler(uploadPackErrorHandler); | |
| addUploadPackFilter(uploadFilter); | |
| setReceivePackFactory(receive); | |
| addReceivePackFilter(receiveFilter); | |
| } | |
| private static String extractWhat(HttpServletRequest request) { | |
| StringBuilder commandName = new StringBuilder(request.getRequestURL()); | |
| if (request.getQueryString() != null) { | |
| commandName.append("?").append(request.getQueryString()); | |
| } | |
| return commandName.toString(); | |
| } | |
| private static ListMultimap<String, String> extractParameters(HttpServletRequest request) { | |
| if (request.getQueryString() == null) { | |
| return ImmutableListMultimap.of(); | |
| } | |
| // Explicit cast is required to compile under Servlet API 2.5, where the return type is raw Map. | |
| @SuppressWarnings("cast") | |
| Map<String, String[]> parameterMap = (Map<String, String[]>) request.getParameterMap(); | |
| ImmutableListMultimap.Builder<String, String> b = ImmutableListMultimap.builder(); | |
| parameterMap.forEach(b::putAll); | |
| return b.build(); | |
| } | |
| static class Resolver implements RepositoryResolver<HttpServletRequest> { | |
| private final GitRepositoryManager manager; | |
| private final PermissionBackend permissionBackend; | |
| private final Provider<CurrentUser> userProvider; | |
| private final ProjectCache projectCache; | |
| @Inject | |
| Resolver( | |
| GitRepositoryManager manager, | |
| PermissionBackend permissionBackend, | |
| Provider<CurrentUser> userProvider, | |
| ProjectCache projectCache) { | |
| this.manager = manager; | |
| this.permissionBackend = permissionBackend; | |
| this.userProvider = userProvider; | |
| this.projectCache = projectCache; | |
| } | |
| @Override | |
| public Repository open(HttpServletRequest req, String projectName) | |
| throws RepositoryNotFoundException, ServiceNotAuthorizedException, | |
| ServiceNotEnabledException, ServiceMayNotContinueException { | |
| while (projectName.endsWith("/")) { | |
| projectName = projectName.substring(0, projectName.length() - 1); | |
| } | |
| if (projectName.endsWith(".git")) { | |
| // Be nice and drop the trailing ".git" suffix, which we never keep | |
| // in our database, but clients might mistakenly provide anyway. | |
| // | |
| projectName = projectName.substring(0, projectName.length() - 4); | |
| while (projectName.endsWith("/")) { | |
| projectName = projectName.substring(0, projectName.length() - 1); | |
| } | |
| } | |
| CurrentUser user = userProvider.get(); | |
| user.setAccessPath(AccessPath.GIT); | |
| try { | |
| Project.NameKey nameKey = Project.nameKey(projectName); | |
| ProjectState state = | |
| projectCache | |
| .get(nameKey) | |
| .orElseThrow(() -> new RepositoryNotFoundException(nameKey.get())); | |
| if (!state.statePermitsRead()) { | |
| throw new RepositoryNotFoundException(nameKey.get()); | |
| } | |
| req.setAttribute(ATT_STATE, state); | |
| try { | |
| permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS); | |
| } catch (AuthException e) { | |
| if (user instanceof AnonymousUser) { | |
| throw new ServiceNotAuthorizedException(); | |
| } | |
| throw new RepositoryNotFoundException(nameKey.get(), e); | |
| } | |
| return manager.openRepository(nameKey); | |
| } catch (IOException | PermissionBackendException err) { | |
| throw new ServiceMayNotContinueException(projectName + " unavailable", err); | |
| } | |
| } | |
| } | |
| static class UploadFactory implements UploadPackFactory<HttpServletRequest> { | |
| private final TransferConfig config; | |
| private final DynamicSet<PreUploadHook> preUploadHooks; | |
| private final DynamicSet<PostUploadHook> postUploadHooks; | |
| private final PluginSetContext<UploadPackInitializer> uploadPackInitializers; | |
| private final PermissionBackend permissionBackend; | |
| @Inject | |
| UploadFactory( | |
| TransferConfig tc, | |
| DynamicSet<PreUploadHook> preUploadHooks, | |
| DynamicSet<PostUploadHook> postUploadHooks, | |
| PluginSetContext<UploadPackInitializer> uploadPackInitializers, | |
| PermissionBackend permissionBackend) { | |
| this.config = tc; | |
| this.preUploadHooks = preUploadHooks; | |
| this.postUploadHooks = postUploadHooks; | |
| this.uploadPackInitializers = uploadPackInitializers; | |
| this.permissionBackend = permissionBackend; | |
| } | |
| @Override | |
| public UploadPack create(HttpServletRequest req, Repository repo) { | |
| ProjectState state = (ProjectState) req.getAttribute(ATT_STATE); | |
| UploadPack up = | |
| new UploadPack( | |
| PermissionAwareRepositoryManager.wrap( | |
| repo, permissionBackend.currentUser().project(state.getNameKey()))); | |
| up.setPackConfig(config.getPackConfig()); | |
| up.setTimeout(config.getTimeout()); | |
| up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks))); | |
| up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks))); | |
| String header = req.getHeader("Git-Protocol"); | |
| if (header != null) { | |
| String[] params = header.split(":"); | |
| up.setExtraParameters(Arrays.asList(params)); | |
| } | |
| uploadPackInitializers.runEach(initializer -> initializer.init(state.getNameKey(), up)); | |
| return up; | |
| } | |
| } | |
| static class UploadFilter implements Filter { | |
| private final UploadValidators.Factory uploadValidatorsFactory; | |
| private final PermissionBackend permissionBackend; | |
| private final Provider<CurrentUser> userProvider; | |
| private final GroupAuditService groupAuditService; | |
| private final Metrics metrics; | |
| private final PluginSetContext<RequestListener> requestListeners; | |
| private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook; | |
| private final Provider<WebSession> sessionProvider; | |
| @Inject | |
| UploadFilter( | |
| UploadValidators.Factory uploadValidatorsFactory, | |
| PermissionBackend permissionBackend, | |
| Provider<CurrentUser> userProvider, | |
| GroupAuditService groupAuditService, | |
| Metrics metrics, | |
| PluginSetContext<RequestListener> requestListeners, | |
| UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook, | |
| Provider<WebSession> sessionProvider) { | |
| this.uploadValidatorsFactory = uploadValidatorsFactory; | |
| this.permissionBackend = permissionBackend; | |
| this.userProvider = userProvider; | |
| this.groupAuditService = groupAuditService; | |
| this.metrics = metrics; | |
| this.requestListeners = requestListeners; | |
| this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook; | |
| this.sessionProvider = sessionProvider; | |
| } | |
| @Override | |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain next) | |
| throws IOException, ServletException { | |
| metrics.requestStarted(); | |
| // The Resolver above already checked READ access for us. | |
| Repository repo = ServletUtils.getRepository(request); | |
| ProjectState state = (ProjectState) request.getAttribute(ATT_STATE); | |
| UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER); | |
| PermissionBackend.ForProject perm = | |
| permissionBackend.currentUser().project(state.getNameKey()); | |
| HttpServletResponseWithStatusWrapper responseWrapper = | |
| new HttpServletResponseWithStatusWrapper((HttpServletResponse) response); | |
| HttpServletRequest httpRequest = (HttpServletRequest) request; | |
| String sessionId = getSessionIdOrNull(sessionProvider); | |
| try (TraceContext traceContext = TraceContext.open()) { | |
| RequestInfo requestInfo = | |
| RequestInfo.builder( | |
| RequestInfo.RequestType.GIT_UPLOAD, userProvider.get(), traceContext) | |
| .project(state.getNameKey()) | |
| .build(); | |
| requestListeners.runEach(l -> l.onRequest(requestInfo)); | |
| try { | |
| if (!perm.test(ProjectPermission.RUN_UPLOAD_PACK)) { | |
| GitSmartHttpTools.sendError( | |
| (HttpServletRequest) request, | |
| responseWrapper, | |
| HttpServletResponse.SC_FORBIDDEN, | |
| "upload-pack not permitted on this server"); | |
| return; | |
| } | |
| } catch (PermissionBackendException e) { | |
| responseWrapper.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); | |
| throw new ServletException(e); | |
| } | |
| // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR | |
| // may have been overridden by a proxy server -- we'll try to avoid this. | |
| UploadValidators uploadValidators = | |
| uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost()); | |
| up.setPreUploadHook( | |
| PreUploadHookChain.newChain( | |
| Lists.newArrayList(up.getPreUploadHook(), uploadValidators))); | |
| if (state.isAllUsers()) { | |
| up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook); | |
| } | |
| try (TracingHook tracingHook = new TracingHook()) { | |
| up.setProtocolV2Hook(tracingHook); | |
| next.doFilter(httpRequest, responseWrapper); | |
| } | |
| } finally { | |
| groupAuditService.dispatch( | |
| new HttpAuditEvent( | |
| sessionId, | |
| userProvider.get(), | |
| extractWhat(httpRequest), | |
| TimeUtil.nowMs(), | |
| extractParameters(httpRequest), | |
| httpRequest.getMethod(), | |
| httpRequest, | |
| responseWrapper.getResponseStatus(), | |
| responseWrapper)); | |
| } | |
| } | |
| @Override | |
| public void init(FilterConfig config) {} | |
| @Override | |
| public void destroy() {} | |
| } | |
| static class GerritUploadPackErrorHandler implements UploadPackErrorHandler { | |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); | |
| @Override | |
| public void upload(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r) | |
| throws IOException { | |
| rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.FAIL.toString()); | |
| try { | |
| r.upload(); | |
| rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.OK.toString()); | |
| } catch (ServiceMayNotContinueException e) { | |
| if (!e.isOutput() && !rsp.isCommitted()) { | |
| rsp.reset(); | |
| sendError(req, rsp, e.getStatusCode(), e.getMessage()); | |
| } | |
| } catch (Throwable e) { | |
| logger.atSevere().withCause(e).log( | |
| "%s", | |
| MessageFormat.format( | |
| HttpServerText.get().internalErrorDuringUploadPack, | |
| ServletUtils.getRepository(req))); | |
| if (!rsp.isCommitted()) { | |
| rsp.reset(); | |
| String msg = e instanceof PackProtocolException ? e.getMessage() : null; | |
| sendError(req, rsp, UploadPackErrorHandler.statusCodeForThrowable(e), msg); | |
| } | |
| } | |
| } | |
| } | |
| static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> { | |
| private final AsyncReceiveCommits.Factory factory; | |
| private final Provider<CurrentUser> userProvider; | |
| @Inject | |
| ReceiveFactory(AsyncReceiveCommits.Factory factory, Provider<CurrentUser> userProvider) { | |
| this.factory = factory; | |
| this.userProvider = userProvider; | |
| } | |
| @Override | |
| public ReceivePack create(HttpServletRequest req, Repository db) | |
| throws ServiceNotAuthorizedException { | |
| final ProjectState state = (ProjectState) req.getAttribute(ATT_STATE); | |
| if (!userProvider.get().isIdentifiedUser()) { | |
| // Anonymous users are not permitted to push. | |
| throw new ServiceNotAuthorizedException(); | |
| } | |
| AsyncReceiveCommits arc = | |
| factory.create(state, userProvider.get().asIdentifiedUser(), db, null); | |
| ReceivePack rp = arc.getReceivePack(); | |
| req.setAttribute(ATT_ARC, arc); | |
| return rp; | |
| } | |
| } | |
| static class DisabledReceiveFactory implements ReceivePackFactory<HttpServletRequest> { | |
| @Override | |
| public ReceivePack create(HttpServletRequest req, Repository db) | |
| throws ServiceNotEnabledException { | |
| throw new ServiceNotEnabledException(); | |
| } | |
| } | |
| static class ReceiveFilter implements Filter { | |
| private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache; | |
| private final PermissionBackend permissionBackend; | |
| private final Provider<CurrentUser> userProvider; | |
| private final GroupAuditService groupAuditService; | |
| private final Metrics metrics; | |
| private final Provider<WebSession> sessionProvider; | |
| @Inject | |
| ReceiveFilter( | |
| @Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache, | |
| PermissionBackend permissionBackend, | |
| Provider<CurrentUser> userProvider, | |
| GroupAuditService groupAuditService, | |
| Metrics metrics, | |
| Provider<WebSession> sessionProvider) { | |
| this.cache = cache; | |
| this.permissionBackend = permissionBackend; | |
| this.userProvider = userProvider; | |
| this.groupAuditService = groupAuditService; | |
| this.metrics = metrics; | |
| this.sessionProvider = sessionProvider; | |
| } | |
| @Override | |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | |
| throws IOException, ServletException { | |
| metrics.requestStarted(); | |
| boolean isGet = "GET".equalsIgnoreCase(((HttpServletRequest) request).getMethod()); | |
| AsyncReceiveCommits arc = (AsyncReceiveCommits) request.getAttribute(ATT_ARC); | |
| // Send refs down the wire. | |
| ReceivePack rp = arc.getReceivePack(); | |
| rp.getAdvertiseRefsHook().advertiseRefs(rp); | |
| ProjectState state = (ProjectState) request.getAttribute(ATT_STATE); | |
| HttpServletResponseWithStatusWrapper responseWrapper = | |
| new HttpServletResponseWithStatusWrapper((HttpServletResponse) response); | |
| HttpServletRequest httpRequest = (HttpServletRequest) request; | |
| Capable canUpload; | |
| try { | |
| try { | |
| if (!permissionBackend | |
| .currentUser() | |
| .project(state.getNameKey()) | |
| .test(ProjectPermission.RUN_RECEIVE_PACK)) { | |
| GitSmartHttpTools.sendError( | |
| httpRequest, | |
| responseWrapper, | |
| HttpServletResponse.SC_FORBIDDEN, | |
| "receive-pack not permitted on this server"); | |
| return; | |
| } | |
| canUpload = arc.canUpload(); | |
| } catch (PermissionBackendException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } finally { | |
| groupAuditService.dispatch( | |
| new HttpAuditEvent( | |
| getSessionIdOrNull(sessionProvider), | |
| userProvider.get(), | |
| extractWhat(httpRequest), | |
| TimeUtil.nowMs(), | |
| extractParameters(httpRequest), | |
| httpRequest.getMethod(), | |
| httpRequest, | |
| responseWrapper.getResponseStatus(), | |
| responseWrapper)); | |
| } | |
| if (canUpload != Capable.OK) { | |
| GitSmartHttpTools.sendError( | |
| httpRequest, | |
| responseWrapper, | |
| HttpServletResponse.SC_FORBIDDEN, | |
| "\n" + canUpload.getMessage()); | |
| return; | |
| } | |
| if (!rp.isCheckReferencedObjectsAreReachable()) { | |
| chain.doFilter(request, responseWrapper); | |
| return; | |
| } | |
| if (!userProvider.get().isIdentifiedUser()) { | |
| chain.doFilter(request, responseWrapper); | |
| return; | |
| } | |
| AdvertisedObjectsCacheKey cacheKey = | |
| AdvertisedObjectsCacheKey.create(userProvider.get().getAccountId(), state.getNameKey()); | |
| if (isGet) { | |
| cache.invalidate(cacheKey); | |
| } else { | |
| Set<ObjectId> ids = cache.getIfPresent(cacheKey); | |
| if (ids != null) { | |
| rp.getAdvertisedObjects().addAll(ids); | |
| cache.invalidate(cacheKey); | |
| } | |
| } | |
| chain.doFilter(request, responseWrapper); | |
| if (isGet) { | |
| cache.put(cacheKey, Collections.unmodifiableSet(new HashSet<>(rp.getAdvertisedObjects()))); | |
| } | |
| } | |
| @Override | |
| public void init(FilterConfig arg0) {} | |
| @Override | |
| public void destroy() {} | |
| } | |
| @Nullable | |
| private static String getSessionIdOrNull(Provider<WebSession> sessionProvider) { | |
| WebSession session = sessionProvider.get(); | |
| if (session.isSignedIn()) { | |
| return session.getSessionId(); | |
| } | |
| return null; | |
| } | |
| } |