LCOV - code coverage report
Current view: top level - sshd/commands - SetAccountCommand.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 58 148 39.2 %
Date: 2022-11-19 15:00:39 Functions: 7 13 53.8 %

          Line data    Source code
       1             : // Copyright (C) 2012 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.commands;
      16             : 
      17             : import static java.nio.charset.StandardCharsets.UTF_8;
      18             : import static java.util.stream.Collectors.toList;
      19             : 
      20             : import com.google.common.base.Strings;
      21             : import com.google.gerrit.common.RawInputUtil;
      22             : import com.google.gerrit.entities.Account;
      23             : import com.google.gerrit.exceptions.EmailException;
      24             : import com.google.gerrit.extensions.api.accounts.EmailInput;
      25             : import com.google.gerrit.extensions.api.accounts.SshKeyInput;
      26             : import com.google.gerrit.extensions.common.EmailInfo;
      27             : import com.google.gerrit.extensions.common.HttpPasswordInput;
      28             : import com.google.gerrit.extensions.common.Input;
      29             : import com.google.gerrit.extensions.common.NameInput;
      30             : import com.google.gerrit.extensions.common.SshKeyInfo;
      31             : import com.google.gerrit.extensions.restapi.AuthException;
      32             : import com.google.gerrit.extensions.restapi.IdString;
      33             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      34             : import com.google.gerrit.extensions.restapi.Response;
      35             : import com.google.gerrit.extensions.restapi.RestApiException;
      36             : import com.google.gerrit.server.CurrentUser;
      37             : import com.google.gerrit.server.IdentifiedUser;
      38             : import com.google.gerrit.server.account.AccountResource;
      39             : import com.google.gerrit.server.account.AccountSshKey;
      40             : import com.google.gerrit.server.account.externalids.ExternalIds;
      41             : import com.google.gerrit.server.permissions.GlobalPermission;
      42             : import com.google.gerrit.server.permissions.PermissionBackend;
      43             : import com.google.gerrit.server.permissions.PermissionBackendException;
      44             : import com.google.gerrit.server.restapi.account.AddSshKey;
      45             : import com.google.gerrit.server.restapi.account.CreateEmail;
      46             : import com.google.gerrit.server.restapi.account.DeleteActive;
      47             : import com.google.gerrit.server.restapi.account.DeleteEmail;
      48             : import com.google.gerrit.server.restapi.account.DeleteExternalIds;
      49             : import com.google.gerrit.server.restapi.account.DeleteSshKey;
      50             : import com.google.gerrit.server.restapi.account.GetEmails;
      51             : import com.google.gerrit.server.restapi.account.GetSshKeys;
      52             : import com.google.gerrit.server.restapi.account.PutActive;
      53             : import com.google.gerrit.server.restapi.account.PutHttpPassword;
      54             : import com.google.gerrit.server.restapi.account.PutName;
      55             : import com.google.gerrit.server.restapi.account.PutPreferred;
      56             : import com.google.gerrit.sshd.CommandMetaData;
      57             : import com.google.gerrit.sshd.SshCommand;
      58             : import com.google.inject.Inject;
      59             : import com.google.inject.Provider;
      60             : import java.io.BufferedReader;
      61             : import java.io.IOException;
      62             : import java.io.InputStreamReader;
      63             : import java.io.UnsupportedEncodingException;
      64             : import java.util.ArrayList;
      65             : import java.util.Collections;
      66             : import java.util.List;
      67             : import org.eclipse.jgit.errors.ConfigInvalidException;
      68             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      69             : import org.kohsuke.args4j.Argument;
      70             : import org.kohsuke.args4j.Option;
      71             : 
      72             : /** Set a user's account settings. * */
      73             : @CommandMetaData(name = "set-account", description = "Change an account's settings")
      74           1 : final class SetAccountCommand extends SshCommand {
      75             : 
      76             :   @Argument(
      77             :       index = 0,
      78             :       required = true,
      79             :       metaVar = "USER",
      80             :       usage = "full name, email-address, ssh username or account id")
      81             :   private Account.Id id;
      82             : 
      83             :   @Option(name = "--full-name", metaVar = "NAME", usage = "display name of the account")
      84             :   private String fullName;
      85             : 
      86             :   @Option(name = "--active", usage = "set account's state to active")
      87             :   private boolean active;
      88             : 
      89             :   @Option(name = "--inactive", usage = "set account's state to inactive")
      90             :   private boolean inactive;
      91             : 
      92           1 :   @Option(name = "--add-email", metaVar = "EMAIL", usage = "email addresses to add to the account")
      93             :   private List<String> addEmails = new ArrayList<>();
      94             : 
      95           1 :   @Option(
      96             :       name = "--delete-email",
      97             :       metaVar = "EMAIL",
      98             :       usage = "email addresses to delete from the account")
      99             :   private List<String> deleteEmails = new ArrayList<>();
     100             : 
     101             :   @Option(
     102             :       name = "--preferred-email",
     103             :       metaVar = "EMAIL",
     104             :       usage = "a registered email address from the account")
     105             :   private String preferredEmail;
     106             : 
     107           1 :   @Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account")
     108             :   private List<String> addSshKeys = new ArrayList<>();
     109             : 
     110           1 :   @Option(
     111             :       name = "--delete-ssh-key",
     112             :       metaVar = "-|KEY",
     113             :       usage = "public keys to delete from the account")
     114             :   private List<String> deleteSshKeys = new ArrayList<>();
     115             : 
     116             :   @Option(
     117             :       name = "--http-password",
     118             :       metaVar = "PASSWORD",
     119             :       usage = "password for HTTP authentication for the account")
     120             :   private String httpPassword;
     121             : 
     122             :   @Option(name = "--clear-http-password", usage = "clear HTTP password for the account")
     123             :   private boolean clearHttpPassword;
     124             : 
     125             :   @Option(name = "--generate-http-password", usage = "generate a new HTTP password for the account")
     126             :   private boolean generateHttpPassword;
     127             : 
     128           1 :   @Option(
     129             :       name = "--delete-external-id",
     130             :       metaVar = "EXTERNALID",
     131             :       usage = "external id to delete from the account")
     132             :   private List<String> externalIdsToDelete = new ArrayList<>();
     133             : 
     134             :   @Inject private IdentifiedUser.GenericFactory genericUserFactory;
     135             : 
     136             :   @Inject private CreateEmail createEmail;
     137             : 
     138             :   @Inject private DeleteExternalIds deleteExternalIds;
     139             : 
     140             :   @Inject private GetEmails getEmails;
     141             : 
     142             :   @Inject private DeleteEmail deleteEmail;
     143             : 
     144             :   @Inject private PutPreferred putPreferred;
     145             : 
     146             :   @Inject private PutName putName;
     147             : 
     148             :   @Inject private PutHttpPassword putHttpPassword;
     149             : 
     150             :   @Inject private PutActive putActive;
     151             : 
     152             :   @Inject private DeleteActive deleteActive;
     153             : 
     154             :   @Inject private AddSshKey addSshKey;
     155             : 
     156             :   @Inject private GetSshKeys getSshKeys;
     157             : 
     158             :   @Inject private DeleteSshKey deleteSshKey;
     159             : 
     160             :   @Inject private PermissionBackend permissionBackend;
     161             : 
     162             :   @Inject private Provider<CurrentUser> userProvider;
     163             : 
     164             :   @Inject private ExternalIds externalIds;
     165             : 
     166             :   private AccountResource rsrc;
     167             : 
     168             :   @Override
     169             :   public void run() throws Exception {
     170           1 :     enableGracefulStop();
     171           1 :     user = genericUserFactory.create(id);
     172             : 
     173           1 :     validate();
     174           1 :     setAccount();
     175           1 :   }
     176             : 
     177             :   private void validate() throws UnloggedFailure {
     178           1 :     PermissionBackend.WithUser userPermission = permissionBackend.user(userProvider.get());
     179             : 
     180           1 :     boolean isAdmin = userPermission.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER);
     181           1 :     boolean canModifyAccount =
     182           1 :         isAdmin || userPermission.testOrFalse(GlobalPermission.MODIFY_ACCOUNT);
     183             : 
     184           1 :     if (!user.hasSameAccountId(userProvider.get()) && !canModifyAccount) {
     185           1 :       throw die(
     186             :           "Setting another user's account information requries 'modify account' or 'administrate server' capabilities.");
     187             :     }
     188           1 :     if (active || inactive) {
     189           0 :       if (!canModifyAccount) {
     190           0 :         throw die(
     191             :             "--active and --inactive require 'modify account' or 'administrate server' capabilities.");
     192             :       }
     193           0 :       if (active && inactive) {
     194           0 :         throw die("--active and --inactive options are mutually exclusive.");
     195             :       }
     196             :     }
     197             : 
     198           1 :     if (generateHttpPassword && clearHttpPassword) {
     199           0 :       throw die("--generate-http-password and --clear-http-password are mutually exclusive.");
     200             :     }
     201           1 :     if (!Strings.isNullOrEmpty(httpPassword)) { // gave --http-password
     202           0 :       if (!isAdmin) {
     203           0 :         throw die("--http-password requires 'administrate server' capabilities.");
     204             :       }
     205           0 :       if (generateHttpPassword) {
     206           0 :         throw die("--http-password and --generate-http-password options are mutually exclusive.");
     207             :       }
     208           0 :       if (clearHttpPassword) {
     209           0 :         throw die("--http-password and --clear-http-password options are mutually exclusive.");
     210             :       }
     211             :     }
     212           1 :     if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) {
     213           0 :       throw die("Only one option may use the stdin");
     214             :     }
     215           1 :     if (deleteSshKeys.contains("ALL")) {
     216           0 :       deleteSshKeys = Collections.singletonList("ALL");
     217             :     }
     218           1 :     if (deleteEmails.contains("ALL")) {
     219           0 :       deleteEmails = Collections.singletonList("ALL");
     220             :     }
     221           1 :     if (deleteEmails.contains(preferredEmail)) {
     222           0 :       throw die(
     223             :           "--preferred-email and --delete-email options are mutually "
     224             :               + "exclusive for the same email address.");
     225             :     }
     226           1 :     if (externalIdsToDelete.contains("ALL")) {
     227           1 :       externalIdsToDelete = Collections.singletonList("ALL");
     228             :     }
     229           1 :   }
     230             : 
     231             :   private void setAccount() throws Failure {
     232           1 :     user = genericUserFactory.create(id);
     233           1 :     rsrc = new AccountResource(user.asIdentifiedUser());
     234             :     try {
     235           1 :       for (String email : addEmails) {
     236           0 :         addEmail(email);
     237           0 :       }
     238             : 
     239           1 :       for (String email : deleteEmails) {
     240           0 :         deleteEmail(email);
     241           0 :       }
     242             : 
     243           1 :       if (preferredEmail != null) {
     244           0 :         putPreferred(preferredEmail);
     245             :       }
     246             : 
     247           1 :       if (fullName != null) {
     248           0 :         NameInput in = new NameInput();
     249           0 :         in.name = fullName;
     250           0 :         putName.apply(rsrc, in);
     251             :       }
     252             : 
     253           1 :       if (httpPassword != null || clearHttpPassword || generateHttpPassword) {
     254           0 :         HttpPasswordInput in = new HttpPasswordInput();
     255           0 :         in.httpPassword = httpPassword;
     256           0 :         if (generateHttpPassword) {
     257           0 :           in.generate = true;
     258             :         }
     259           0 :         Response<String> resp = putHttpPassword.apply(rsrc, in);
     260           0 :         if (generateHttpPassword) {
     261           0 :           stdout.print("New password: " + resp.value() + "\n");
     262             :         }
     263             :       }
     264             : 
     265           1 :       if (active) {
     266           0 :         putActive.apply(rsrc, null);
     267           1 :       } else if (inactive) {
     268             :         try {
     269           0 :           deleteActive.apply(rsrc, null);
     270           0 :         } catch (ResourceNotFoundException e) {
     271             :           // user is already inactive
     272           0 :         }
     273             :       }
     274             : 
     275           1 :       addSshKeys = readSshKey(addSshKeys);
     276           1 :       if (!addSshKeys.isEmpty()) {
     277           0 :         addSshKeys(addSshKeys);
     278             :       }
     279             : 
     280           1 :       deleteSshKeys = readSshKey(deleteSshKeys);
     281           1 :       if (!deleteSshKeys.isEmpty()) {
     282           0 :         deleteSshKeys(deleteSshKeys);
     283             :       }
     284             : 
     285           1 :       for (String externalId : externalIdsToDelete) {
     286           1 :         deleteExternalId(externalId);
     287           1 :       }
     288           1 :     } catch (RestApiException e) {
     289           1 :       throw die(e.getMessage());
     290           0 :     } catch (Exception e) {
     291           0 :       throw new Failure(1, "unavailable", e);
     292           1 :     }
     293           1 :   }
     294             : 
     295             :   private void addSshKeys(List<String> sshKeys)
     296             :       throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
     297           0 :     for (String sshKey : sshKeys) {
     298           0 :       SshKeyInput in = new SshKeyInput();
     299           0 :       in.raw = RawInputUtil.create(sshKey.getBytes(UTF_8), "text/plain");
     300           0 :       addSshKey.apply(rsrc, in);
     301           0 :     }
     302           0 :   }
     303             : 
     304             :   private void deleteSshKeys(List<String> sshKeys) throws Exception {
     305           0 :     List<SshKeyInfo> infos = getSshKeys.apply(rsrc).value();
     306           0 :     if (sshKeys.contains("ALL")) {
     307           0 :       for (SshKeyInfo i : infos) {
     308           0 :         deleteSshKey(i);
     309           0 :       }
     310             :     } else {
     311           0 :       for (String sshKey : sshKeys) {
     312           0 :         for (SshKeyInfo i : infos) {
     313           0 :           if (sshKey.trim().equals(i.sshPublicKey) || sshKey.trim().equals(i.comment)) {
     314           0 :             deleteSshKey(i);
     315             :           }
     316           0 :         }
     317           0 :       }
     318             :     }
     319           0 :   }
     320             : 
     321             :   private void deleteSshKey(SshKeyInfo i)
     322             :       throws AuthException, RepositoryNotFoundException, IOException, ConfigInvalidException,
     323             :           PermissionBackendException {
     324           0 :     AccountSshKey sshKey = AccountSshKey.create(user.getAccountId(), i.seq, i.sshPublicKey);
     325           0 :     deleteSshKey.apply(new AccountResource.SshKey(user.asIdentifiedUser(), sshKey), null);
     326           0 :   }
     327             : 
     328             :   private void addEmail(String email)
     329             :       throws UnloggedFailure, RestApiException, IOException, ConfigInvalidException,
     330             :           PermissionBackendException {
     331           0 :     EmailInput in = new EmailInput();
     332           0 :     in.email = email;
     333           0 :     in.noConfirmation = true;
     334             :     try {
     335           0 :       createEmail.apply(rsrc, IdString.fromDecoded(email), in);
     336           0 :     } catch (EmailException e) {
     337           0 :       throw die(e.getMessage());
     338           0 :     }
     339           0 :   }
     340             : 
     341             :   private void deleteEmail(String email) throws Exception {
     342           0 :     if (email.equals("ALL")) {
     343           0 :       List<EmailInfo> emails = getEmails.apply(rsrc).value();
     344           0 :       for (EmailInfo e : emails) {
     345           0 :         deleteEmail.apply(new AccountResource.Email(user.asIdentifiedUser(), e.email), new Input());
     346           0 :       }
     347           0 :     } else {
     348           0 :       deleteEmail.apply(new AccountResource.Email(user.asIdentifiedUser(), email), new Input());
     349             :     }
     350           0 :   }
     351             : 
     352             :   private void putPreferred(String email) throws Exception {
     353           0 :     for (EmailInfo e : getEmails.apply(rsrc).value()) {
     354           0 :       if (e.email.equals(email)) {
     355           0 :         putPreferred.apply(new AccountResource.Email(user.asIdentifiedUser(), email), null);
     356           0 :         return;
     357             :       }
     358           0 :     }
     359           0 :     stderr.println("preferred email not found: " + email);
     360           0 :   }
     361             : 
     362             :   private List<String> readSshKey(List<String> sshKeys)
     363             :       throws UnsupportedEncodingException, IOException {
     364           1 :     if (!sshKeys.isEmpty()) {
     365           0 :       int idx = sshKeys.indexOf("-");
     366           0 :       if (idx >= 0) {
     367           0 :         StringBuilder sshKey = new StringBuilder();
     368           0 :         BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8));
     369             :         String line;
     370           0 :         while ((line = br.readLine()) != null) {
     371           0 :           sshKey.append(line).append("\n");
     372             :         }
     373           0 :         sshKeys.set(idx, sshKey.toString());
     374             :       }
     375             :     }
     376           1 :     return sshKeys;
     377             :   }
     378             : 
     379             :   private void deleteExternalId(String externalId)
     380             :       throws IOException, RestApiException, ConfigInvalidException, PermissionBackendException {
     381             :     List<String> ids;
     382           1 :     if (externalId.equals("ALL")) {
     383           1 :       ids =
     384           1 :           externalIds.byAccount(rsrc.getUser().getAccountId()).stream()
     385           1 :               .map(e -> e.key().get())
     386           1 :               .collect(toList());
     387           1 :       if (ids.isEmpty()) {
     388           0 :         throw new ResourceNotFoundException("Account has no external Ids");
     389             :       }
     390             :     } else {
     391           1 :       ids = Collections.singletonList(externalId);
     392             :     }
     393           1 :     deleteExternalIds.apply(rsrc, ids);
     394           1 :   }
     395             : }

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