LCOV - code coverage report
Current view: top level - acceptance - InProcessProtocol.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 115 134 85.8 %
Date: 2022-11-19 15:00:39 Functions: 22 27 81.5 %

          Line data    Source code
       1             : // Copyright (C) 2015 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.acceptance;
      16             : 
      17             : import static com.google.gerrit.server.git.receive.LazyPostReceiveHookChain.affectsSize;
      18             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      19             : import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
      20             : 
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.common.collect.Lists;
      23             : import com.google.gerrit.acceptance.InProcessProtocol.Context;
      24             : import com.google.gerrit.common.data.Capable;
      25             : import com.google.gerrit.entities.Account;
      26             : import com.google.gerrit.entities.Project;
      27             : import com.google.gerrit.extensions.registration.DynamicSet;
      28             : import com.google.gerrit.extensions.restapi.AuthException;
      29             : import com.google.gerrit.server.AccessPath;
      30             : import com.google.gerrit.server.CurrentUser;
      31             : import com.google.gerrit.server.IdentifiedUser;
      32             : import com.google.gerrit.server.RemotePeer;
      33             : import com.google.gerrit.server.RequestCleanup;
      34             : import com.google.gerrit.server.config.GerritRequestModule;
      35             : import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
      36             : import com.google.gerrit.server.git.ReceivePackInitializer;
      37             : import com.google.gerrit.server.git.TransferConfig;
      38             : import com.google.gerrit.server.git.UploadPackInitializer;
      39             : import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
      40             : import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
      41             : import com.google.gerrit.server.git.validators.UploadValidators;
      42             : import com.google.gerrit.server.permissions.PermissionBackend;
      43             : import com.google.gerrit.server.permissions.PermissionBackendException;
      44             : import com.google.gerrit.server.permissions.ProjectPermission;
      45             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      46             : import com.google.gerrit.server.project.ProjectCache;
      47             : import com.google.gerrit.server.project.ProjectState;
      48             : import com.google.gerrit.server.quota.QuotaBackend;
      49             : import com.google.gerrit.server.quota.QuotaException;
      50             : import com.google.gerrit.server.quota.QuotaResponse;
      51             : import com.google.gerrit.server.util.RequestContext;
      52             : import com.google.gerrit.server.util.RequestScopePropagator;
      53             : import com.google.gerrit.server.util.ThreadLocalRequestContext;
      54             : import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
      55             : import com.google.inject.AbstractModule;
      56             : import com.google.inject.Inject;
      57             : import com.google.inject.Key;
      58             : import com.google.inject.Module;
      59             : import com.google.inject.OutOfScopeException;
      60             : import com.google.inject.Provider;
      61             : import com.google.inject.Provides;
      62             : import com.google.inject.Scope;
      63             : import com.google.inject.servlet.RequestScoped;
      64             : import java.io.IOException;
      65             : import java.net.SocketAddress;
      66             : import java.util.HashMap;
      67             : import java.util.List;
      68             : import java.util.Map;
      69             : import org.eclipse.jgit.lib.Repository;
      70             : import org.eclipse.jgit.transport.PostReceiveHook;
      71             : import org.eclipse.jgit.transport.PostReceiveHookChain;
      72             : import org.eclipse.jgit.transport.PreUploadHook;
      73             : import org.eclipse.jgit.transport.PreUploadHookChain;
      74             : import org.eclipse.jgit.transport.ReceivePack;
      75             : import org.eclipse.jgit.transport.TestProtocol;
      76             : import org.eclipse.jgit.transport.UploadPack;
      77             : import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
      78             : import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
      79             : import org.eclipse.jgit.transport.resolver.UploadPackFactory;
      80             : 
      81             : class InProcessProtocol extends TestProtocol<Context> {
      82             :   static Module module() {
      83         138 :     return new AbstractModule() {
      84             :       @Override
      85             :       public void configure() {
      86         138 :         install(new GerritRequestModule());
      87         138 :         bind(RequestScopePropagator.class).to(Propagator.class);
      88         138 :         bindScope(RequestScoped.class, InProcessProtocol.REQUEST);
      89         138 :       }
      90             : 
      91             :       @Provides
      92             :       @RemotePeer
      93             :       SocketAddress getSocketAddress() {
      94           0 :         throw new OutOfScopeException("No remote peer in acceptance tests");
      95             :       }
      96             :     };
      97             :   }
      98             : 
      99         138 :   private static final Scope REQUEST =
     100         138 :       new Scope() {
     101             :         @Override
     102             :         public <T> Provider<T> scope(Key<T> key, Provider<T> creator) {
     103         138 :           return new Provider<>() {
     104             :             @Override
     105             :             public T get() {
     106          90 :               Context ctx = current.get();
     107          90 :               if (ctx == null) {
     108           0 :                 throw new OutOfScopeException("Not in TestProtocol scope");
     109             :               }
     110          90 :               return ctx.get(key, creator);
     111             :             }
     112             : 
     113             :             @Override
     114             :             public String toString() {
     115           0 :               return String.format("%s[%s]", creator, REQUEST);
     116             :             }
     117             :           };
     118             :         }
     119             : 
     120             :         @Override
     121             :         public String toString() {
     122           0 :           return "InProcessProtocol.REQUEST";
     123             :         }
     124             :       };
     125             : 
     126             :   private static class Propagator extends ThreadLocalRequestScopePropagator<Context> {
     127             :     @Inject
     128             :     Propagator(ThreadLocalRequestContext local) {
     129          91 :       super(REQUEST, current, local);
     130          91 :     }
     131             : 
     132             :     @Override
     133             :     protected Context continuingContext(Context ctx) {
     134          90 :       return ctx.newContinuingContext();
     135             :     }
     136             :   }
     137             : 
     138         138 :   private static final ThreadLocal<Context> current = new ThreadLocal<>();
     139             : 
     140             :   // TODO(dborowitz): Merge this with AcceptanceTestRequestScope.
     141             :   /**
     142             :    * Multi-purpose session/context object.
     143             :    *
     144             :    * <p>Confusingly, Gerrit has two ideas of what a "context" object is: one for Guice {@link
     145             :    * RequestScoped}, and one for its own simplified version of request scoping using {@link
     146             :    * ThreadLocalRequestContext}. This class provides both, in essence just delegating the {@code
     147             :    * ThreadLocalRequestContext} scoping to the Guice scoping mechanism.
     148             :    *
     149             :    * <p>It is also used as the session type for {@code UploadPackFactory} and {@code
     150             :    * ReceivePackFactory}, since, after all, it encapsulates all the information about a single
     151             :    * request.
     152             :    */
     153             :   static class Context implements RequestContext {
     154         132 :     private static final Key<RequestCleanup> RC_KEY = Key.get(RequestCleanup.class);
     155         132 :     private static final Key<CurrentUser> USER_KEY = Key.get(CurrentUser.class);
     156             : 
     157             :     private final IdentifiedUser.GenericFactory userFactory;
     158             :     private final Account.Id accountId;
     159             :     private final Project.NameKey project;
     160             :     private final RequestCleanup cleanup;
     161             :     private final Map<Key<?>, Object> map;
     162             : 
     163             :     Context(
     164         132 :         IdentifiedUser.GenericFactory userFactory, Account.Id accountId, Project.NameKey project) {
     165         132 :       this.userFactory = userFactory;
     166         132 :       this.accountId = accountId;
     167         132 :       this.project = project;
     168         132 :       map = new HashMap<>();
     169         132 :       cleanup = new RequestCleanup();
     170         132 :       map.put(RC_KEY, cleanup);
     171             : 
     172         132 :       IdentifiedUser user = userFactory.create(accountId);
     173         132 :       user.setAccessPath(AccessPath.GIT);
     174         132 :       map.put(USER_KEY, user);
     175         132 :     }
     176             : 
     177             :     private Context newContinuingContext() {
     178          90 :       return new Context(userFactory, accountId, project);
     179             :     }
     180             : 
     181             :     @Override
     182             :     public CurrentUser getUser() {
     183         132 :       return get(USER_KEY, null);
     184             :     }
     185             : 
     186             :     private synchronized <T> T get(Key<T> key, Provider<T> creator) {
     187             :       @SuppressWarnings("unchecked")
     188         132 :       T t = (T) map.get(key);
     189         132 :       if (t == null) {
     190           0 :         t = creator.get();
     191           0 :         map.put(key, t);
     192             :       }
     193         132 :       return t;
     194             :     }
     195             :   }
     196             : 
     197             :   private static class Upload implements UploadPackFactory<Context> {
     198             :     private final TransferConfig transferConfig;
     199             :     private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
     200             :     private final DynamicSet<PreUploadHook> preUploadHooks;
     201             :     private final UploadValidators.Factory uploadValidatorsFactory;
     202             :     private final ThreadLocalRequestContext threadContext;
     203             :     private final ProjectCache projectCache;
     204             :     private final PermissionBackend permissionBackend;
     205             :     private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
     206             : 
     207             :     @Inject
     208             :     Upload(
     209             :         TransferConfig transferConfig,
     210             :         PluginSetContext<UploadPackInitializer> uploadPackInitializers,
     211             :         DynamicSet<PreUploadHook> preUploadHooks,
     212             :         UploadValidators.Factory uploadValidatorsFactory,
     213             :         ThreadLocalRequestContext threadContext,
     214             :         ProjectCache projectCache,
     215             :         PermissionBackend permissionBackend,
     216         132 :         UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook) {
     217         132 :       this.transferConfig = transferConfig;
     218         132 :       this.uploadPackInitializers = uploadPackInitializers;
     219         132 :       this.preUploadHooks = preUploadHooks;
     220         132 :       this.uploadValidatorsFactory = uploadValidatorsFactory;
     221         132 :       this.threadContext = threadContext;
     222         132 :       this.projectCache = projectCache;
     223         132 :       this.permissionBackend = permissionBackend;
     224         132 :       this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
     225         132 :     }
     226             : 
     227             :     @Override
     228             :     public UploadPack create(Context req, Repository repo) throws ServiceNotAuthorizedException {
     229             :       // Set the request context, but don't bother unsetting, since we don't
     230             :       // have an easy way to run code when this instance is done being used.
     231             :       // Each operation is run in its own thread, so we don't need to recover
     232             :       // its original context anyway.
     233         132 :       threadContext.setContext(req);
     234         132 :       current.set(req);
     235             : 
     236         132 :       PermissionBackend.ForProject perm = permissionBackend.currentUser().project(req.project);
     237             :       try {
     238         132 :         if (!perm.test(ProjectPermission.RUN_UPLOAD_PACK)) {
     239           0 :           throw new ServiceNotAuthorizedException("upload pack not permitted");
     240             :         }
     241           0 :       } catch (PermissionBackendException e) {
     242           0 :         throw new RuntimeException(e);
     243         132 :       }
     244             : 
     245         132 :       ProjectState projectState =
     246         132 :           projectCache.get(req.project).orElseThrow(illegalState(req.project));
     247         132 :       Repository permissionAwareRepository = PermissionAwareRepositoryManager.wrap(repo, perm);
     248         132 :       UploadPack up = new UploadPack(permissionAwareRepository);
     249         132 :       up.setPackConfig(transferConfig.getPackConfig());
     250         132 :       up.setTimeout(transferConfig.getTimeout());
     251         132 :       if (projectState.isAllUsers()) {
     252           5 :         up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
     253             :       }
     254         132 :       List<PreUploadHook> hooks = Lists.newArrayList(preUploadHooks);
     255         132 :       hooks.add(
     256         132 :           uploadValidatorsFactory.create(
     257         132 :               projectState.getProject(), permissionAwareRepository, "localhost-test"));
     258         132 :       up.setPreUploadHook(PreUploadHookChain.newChain(hooks));
     259         132 :       uploadPackInitializers.runEach(initializer -> initializer.init(req.project, up));
     260         132 :       return up;
     261             :     }
     262             :   }
     263             : 
     264             :   private static class Receive implements ReceivePackFactory<Context> {
     265             :     private final Provider<CurrentUser> userProvider;
     266             :     private final ProjectCache projectCache;
     267             :     private final AsyncReceiveCommits.Factory factory;
     268             :     private final TransferConfig config;
     269             :     private final PluginSetContext<ReceivePackInitializer> receivePackInitializers;
     270             :     private final DynamicSet<PostReceiveHook> postReceiveHooks;
     271             :     private final ThreadLocalRequestContext threadContext;
     272             :     private final PermissionBackend permissionBackend;
     273             :     private final QuotaBackend quotaBackend;
     274             : 
     275             :     @Inject
     276             :     Receive(
     277             :         Provider<CurrentUser> userProvider,
     278             :         ProjectCache projectCache,
     279             :         AsyncReceiveCommits.Factory factory,
     280             :         TransferConfig config,
     281             :         PluginSetContext<ReceivePackInitializer> receivePackInitializers,
     282             :         DynamicSet<PostReceiveHook> postReceiveHooks,
     283             :         ThreadLocalRequestContext threadContext,
     284             :         PermissionBackend permissionBackend,
     285         132 :         QuotaBackend quotaBackend) {
     286         132 :       this.userProvider = userProvider;
     287         132 :       this.projectCache = projectCache;
     288         132 :       this.factory = factory;
     289         132 :       this.config = config;
     290         132 :       this.receivePackInitializers = receivePackInitializers;
     291         132 :       this.postReceiveHooks = postReceiveHooks;
     292         132 :       this.threadContext = threadContext;
     293         132 :       this.permissionBackend = permissionBackend;
     294         132 :       this.quotaBackend = quotaBackend;
     295         132 :     }
     296             : 
     297             :     @Override
     298             :     public ReceivePack create(Context req, Repository db) throws ServiceNotAuthorizedException {
     299             :       // Set the request context, but don't bother unsetting, since we don't
     300             :       // have an easy way to run code when this instance is done being used.
     301             :       // Each operation is run in its own thread, so we don't need to recover
     302             :       // its original context anyway.
     303          91 :       threadContext.setContext(req);
     304          91 :       current.set(req);
     305             :       try {
     306          91 :         permissionBackend
     307          91 :             .currentUser()
     308          91 :             .project(req.project)
     309          91 :             .check(ProjectPermission.RUN_RECEIVE_PACK);
     310           0 :       } catch (AuthException e) {
     311           0 :         throw new ServiceNotAuthorizedException(e.getMessage(), e);
     312           0 :       } catch (PermissionBackendException e) {
     313           0 :         throw new RuntimeException(e);
     314          91 :       }
     315             :       try {
     316          91 :         IdentifiedUser identifiedUser = userProvider.get().asIdentifiedUser();
     317          91 :         ProjectState projectState =
     318             :             projectCache
     319          91 :                 .get(req.project)
     320          91 :                 .orElseThrow(
     321           0 :                     () -> new RuntimeException(String.format("project %s not found", req.project)));
     322             : 
     323          91 :         AsyncReceiveCommits arc = factory.create(projectState, identifiedUser, db, null);
     324          91 :         if (arc.canUpload() != Capable.OK) {
     325           0 :           throw new ServiceNotAuthorizedException();
     326             :         }
     327             : 
     328          91 :         ReceivePack rp = arc.getReceivePack();
     329          91 :         rp.setRefLogIdent(identifiedUser.newRefLogIdent());
     330          91 :         rp.setTimeout(config.getTimeout());
     331          91 :         rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
     332             : 
     333          91 :         receivePackInitializers.runEach(
     334           3 :             initializer -> initializer.init(projectState.getNameKey(), rp));
     335          91 :         QuotaResponse.Aggregated availableTokens =
     336             :             quotaBackend
     337          91 :                 .user(identifiedUser)
     338          91 :                 .project(req.project)
     339          91 :                 .availableTokens(REPOSITORY_SIZE_GROUP);
     340          91 :         availableTokens.throwOnError();
     341          91 :         availableTokens.availableTokens().ifPresent(rp::setMaxPackSizeLimit);
     342             : 
     343             :         ImmutableList<PostReceiveHook> hooks =
     344          91 :             ImmutableList.<PostReceiveHook>builder()
     345          91 :                 .add(
     346             :                     (pack, commands) -> {
     347          91 :                       if (affectsSize(pack)) {
     348             :                         try {
     349           3 :                           quotaBackend
     350           3 :                               .user(identifiedUser)
     351           3 :                               .project(req.project)
     352           3 :                               .requestTokens(REPOSITORY_SIZE_GROUP, pack.getPackSize())
     353           3 :                               .throwOnError();
     354           0 :                         } catch (QuotaException e) {
     355           0 :                           throw new RuntimeException(e);
     356           3 :                         }
     357             :                       }
     358          91 :                     })
     359          91 :                 .addAll(postReceiveHooks)
     360          91 :                 .build();
     361          91 :         rp.setPostReceiveHook(PostReceiveHookChain.newChain(hooks));
     362          91 :         return rp;
     363           0 :       } catch (IOException | PermissionBackendException | QuotaException e) {
     364           0 :         throw new RuntimeException(e);
     365             :       }
     366             :     }
     367             :   }
     368             : 
     369             :   @Inject
     370             :   InProcessProtocol(Upload uploadPackFactory, Receive receivePackFactory) {
     371         132 :     super(uploadPackFactory, receivePackFactory);
     372         132 :   }
     373             : }

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