LCOV - code coverage report
Current view: top level - sshd - DispatchCommand.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 79 94 84.0 %
Date: 2022-11-19 15:00:39 Functions: 6 7 85.7 %

          Line data    Source code
       1             : // Copyright (C) 2009 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.sshd;
      16             : 
      17             : import com.google.common.base.Strings;
      18             : import com.google.common.base.Throwables;
      19             : import com.google.common.collect.Sets;
      20             : import com.google.common.util.concurrent.Atomics;
      21             : import com.google.gerrit.extensions.registration.DynamicSet;
      22             : import com.google.gerrit.extensions.restapi.AuthException;
      23             : import com.google.gerrit.server.DynamicOptions;
      24             : import com.google.gerrit.server.args4j.SubcommandHandler;
      25             : import com.google.gerrit.server.permissions.GlobalPermission;
      26             : import com.google.gerrit.server.permissions.PermissionBackend;
      27             : import com.google.gerrit.server.permissions.PermissionBackendException;
      28             : import com.google.inject.Inject;
      29             : import com.google.inject.assistedinject.Assisted;
      30             : import java.io.IOException;
      31             : import java.io.StringWriter;
      32             : import java.util.ArrayList;
      33             : import java.util.List;
      34             : import java.util.Map;
      35             : import java.util.concurrent.atomic.AtomicReference;
      36             : import org.apache.sshd.server.Environment;
      37             : import org.apache.sshd.server.channel.ChannelSession;
      38             : import org.apache.sshd.server.command.Command;
      39             : import org.kohsuke.args4j.Argument;
      40             : 
      41             : /** Command that dispatches to a subcommand from its command table. */
      42             : final class DispatchCommand extends BaseCommand {
      43             :   interface Factory {
      44             :     DispatchCommand create(Map<String, CommandProvider> map);
      45             :   }
      46             : 
      47             :   private final PermissionBackend permissionBackend;
      48             :   private final Map<String, CommandProvider> commands;
      49             :   private final AtomicReference<Command> atomicCmd;
      50             :   private final DynamicSet<SshExecuteCommandInterceptor> commandInterceptors;
      51             : 
      52             :   @Argument(index = 0, required = false, metaVar = "COMMAND", handler = SubcommandHandler.class)
      53             :   private String commandName;
      54             : 
      55           9 :   @Argument(index = 1, multiValued = true, metaVar = "ARG")
      56             :   private List<String> args = new ArrayList<>();
      57             : 
      58             :   @Inject
      59             :   DispatchCommand(
      60             :       PermissionBackend permissionBackend,
      61             :       DynamicSet<SshExecuteCommandInterceptor> commandInterceptors,
      62           9 :       @Assisted Map<String, CommandProvider> all) {
      63           9 :     this.permissionBackend = permissionBackend;
      64           9 :     commands = all;
      65           9 :     atomicCmd = Atomics.newReference();
      66           9 :     this.commandInterceptors = commandInterceptors;
      67           9 :   }
      68             : 
      69             :   Map<String, CommandProvider> getMap() {
      70           0 :     return commands;
      71             :   }
      72             : 
      73             :   @Override
      74             :   public void start(ChannelSession channel, Environment env) throws IOException {
      75           9 :     try (DynamicOptions pluginOptions = new DynamicOptions(injector, dynamicBeans)) {
      76           9 :       parseCommandLine(pluginOptions);
      77           9 :       if (Strings.isNullOrEmpty(commandName)) {
      78           0 :         StringWriter msg = new StringWriter();
      79           0 :         msg.write(usage());
      80           0 :         throw die(msg.toString());
      81             :       }
      82             : 
      83           9 :       final CommandProvider p = commands.get(commandName);
      84           9 :       if (p == null) {
      85             :         String msg =
      86           1 :             (getName().isEmpty() ? "Gerrit Code Review" : getName())
      87             :                 + ": "
      88             :                 + commandName
      89             :                 + ": not found";
      90           1 :         throw die(msg);
      91             :       }
      92             : 
      93           9 :       final Command cmd = p.getProvider().get();
      94           9 :       checkRequiresCapability(cmd);
      95           9 :       String actualCommandName = commandName;
      96           9 :       if (cmd instanceof BaseCommand) {
      97           9 :         final BaseCommand bc = (BaseCommand) cmd;
      98           9 :         if (!getName().isEmpty()) {
      99           5 :           actualCommandName = getName() + " " + commandName;
     100             :         }
     101           9 :         bc.setName(actualCommandName);
     102           9 :         bc.setArguments(args.toArray(new String[args.size()]));
     103             : 
     104           9 :       } else if (!args.isEmpty()) {
     105           0 :         throw die(commandName + " does not take arguments");
     106             :       }
     107             : 
     108           9 :       for (SshExecuteCommandInterceptor commandInterceptor : commandInterceptors) {
     109           0 :         if (!commandInterceptor.accept(actualCommandName, args)) {
     110           0 :           throw new UnloggedFailure(
     111             :               126,
     112           0 :               String.format(
     113             :                   "blocked by %s, contact gerrit administrators for more details",
     114           0 :                   commandInterceptor.name()));
     115             :         }
     116           0 :       }
     117             : 
     118           9 :       provideStateTo(cmd);
     119           9 :       atomicCmd.set(cmd);
     120           9 :       cmd.start(channel, env);
     121             : 
     122           1 :     } catch (UnloggedFailure e) {
     123           1 :       String msg = e.getMessage();
     124           1 :       if (!msg.endsWith("\n")) {
     125           1 :         msg += "\n";
     126             :       }
     127           1 :       err.write(msg.getBytes(ENC));
     128           1 :       err.flush();
     129           1 :       onExit(e.exitCode);
     130           9 :     }
     131           9 :   }
     132             : 
     133             :   private void checkRequiresCapability(Command cmd) throws UnloggedFailure {
     134           9 :     String pluginName = null;
     135           9 :     if (cmd instanceof BaseCommand) {
     136           9 :       pluginName = ((BaseCommand) cmd).getPluginName();
     137             :     }
     138             :     try {
     139           9 :       permissionBackend
     140           9 :           .currentUser()
     141           9 :           .checkAny(GlobalPermission.fromAnnotation(pluginName, cmd.getClass()));
     142           1 :     } catch (AuthException e) {
     143           1 :       throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage());
     144           0 :     } catch (PermissionBackendException e) {
     145           0 :       throw new UnloggedFailure(1, "fatal: permission check unavailable", e);
     146           9 :     }
     147           9 :   }
     148             : 
     149             :   @Override
     150             :   public void destroy(ChannelSession channel) {
     151           9 :     Command cmd = atomicCmd.getAndSet(null);
     152           9 :     if (cmd != null) {
     153             :       try {
     154           9 :         cmd.destroy(channel);
     155           0 :       } catch (Exception e) {
     156           0 :         Throwables.throwIfUnchecked(e);
     157           0 :         throw new RuntimeException(e);
     158           9 :       }
     159             :     }
     160           9 :   }
     161             : 
     162             :   @Override
     163             :   protected String usage() {
     164           1 :     final StringBuilder usage = new StringBuilder();
     165           1 :     usage.append("Available commands");
     166           1 :     if (!getName().isEmpty()) {
     167           1 :       usage.append(" of ");
     168           1 :       usage.append(getName());
     169             :     }
     170           1 :     usage.append(" are:\n");
     171           1 :     usage.append("\n");
     172             : 
     173           1 :     int maxLength = -1;
     174           1 :     for (String name : commands.keySet()) {
     175           1 :       maxLength = Math.max(maxLength, name.length());
     176           1 :     }
     177           1 :     String format = "%-" + maxLength + "s   %s";
     178           1 :     for (String name : Sets.newTreeSet(commands.keySet())) {
     179           1 :       final CommandProvider p = commands.get(name);
     180           1 :       usage.append("   ");
     181           1 :       usage.append(String.format(format, name, Strings.nullToEmpty(p.getDescription())));
     182           1 :       usage.append("\n");
     183           1 :     }
     184           1 :     usage.append("\n");
     185             : 
     186           1 :     usage.append("See '");
     187           1 :     if (getName().indexOf(' ') < 0) {
     188           1 :       usage.append(getName());
     189           1 :       usage.append(' ');
     190             :     }
     191           1 :     usage.append("COMMAND --help' for more information.\n");
     192           1 :     usage.append("\n");
     193           1 :     return usage.toString();
     194             :   }
     195             : 
     196             :   public String getCommandName() {
     197           9 :     return commandName;
     198             :   }
     199             : }

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