Allow to delegate modify account commands to non administrators

Currently only administrators groups allow to modify accounts. This
is impractical on very big installations. Add modify account global
capability and allow administrators to optionally delegate this job.

Bug: Issue 2786
Change-Id: Icc105c39e76908a075e89ec14921972b91866dd4
This commit is contained in:
David Ostrovsky
2014-07-22 00:55:47 +02:00
committed by David Pursehouse
parent e715d2484a
commit aa49e277a3
20 changed files with 48 additions and 27 deletions

View File

@@ -1208,6 +1208,12 @@ kill command ends tasks that currently occupy the Gerrit server, usually
a replication task or a user initiated task such as an upload-pack or
receive-pack.
[[capability_modifyAccount]]
=== Modify Account
Allow to link:cmd-set-account.html[modify accounts over the ssh prompt].
This capability allows the granted group members to modify any user account
setting.
[[capability_priority]]
=== Priority

View File

@@ -21,7 +21,15 @@ It also allows managing email addresses, which bypasses the
verification step we force within the UI.
== ACCESS
Caller must be a member of the privileged 'Administrators' group.
Caller must be a member of the privileged 'Administrators' group,
or have been granted
link:access-control.html#capability_modifyAccount[the 'Modify Account' global capability].
To set the HTTP password for the user account (option --http-password) the
caller must be a member of the privileged 'Administrators' group,
or have been granted
link:access-control.html#capability_generateHttpPassword[the 'Generate HTTP Password' global capability]
in addition to 'Modify Account' global capability.
== SCRIPTING
This command is intended to be used in scripts.

View File

@@ -38,6 +38,9 @@ public class GlobalCapability {
/** Can create any account on the server. */
public static final String CREATE_ACCOUNT = "createAccount";
/** Can modify any account on the server. */
public static final String MODIFY_ACCOUNT = "modifyAccount";
/** Can create any group on the server. */
public static final String CREATE_GROUP = "createGroup";
@@ -108,6 +111,7 @@ public class GlobalCapability {
NAMES_ALL.add(FLUSH_CACHES);
NAMES_ALL.add(GENERATE_HTTP_PASSWORD);
NAMES_ALL.add(KILL_TASK);
NAMES_ALL.add(MODIFY_ACCOUNT);
NAMES_ALL.add(PRIORITY);
NAMES_ALL.add(QUERY_LIMIT);
NAMES_ALL.add(RUN_AS);

View File

@@ -63,7 +63,7 @@ public class AddSshKey implements RestModifyView<AccountResource, Input> {
public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
throws AuthException, BadRequestException, OrmException, IOException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to add SSH keys");
}
return apply(rsrc.getUser(), input);

View File

@@ -110,6 +110,12 @@ public class CapabilityControl {
|| canAdministrateServer();
}
/** @return true if the user can modify an account for another user. */
public boolean canModifyAccount() {
return canPerform(GlobalCapability.MODIFY_ACCOUNT)
|| canAdministrateServer();
}
/** @return true if the user can view all accounts. */
public boolean canViewAllAccounts() {
return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS)

View File

@@ -85,7 +85,7 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> {
ResourceNotFoundException, OrmException, EmailException,
MethodNotAllowedException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to add email address");
}
@@ -98,8 +98,8 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> {
}
if (input.noConfirmation
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("must be administrator to use no_confirmation");
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to use no_confirmation");
}
return apply(rsrc.getUser(), input);

View File

@@ -29,7 +29,7 @@ import com.google.inject.Singleton;
import java.util.Collections;
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class DeleteActive implements RestModifyView<AccountResource, Input> {
public static class Input {

View File

@@ -55,7 +55,7 @@ public class DeleteEmail implements RestModifyView<AccountResource.Email, Input>
throws AuthException, ResourceNotFoundException,
ResourceConflictException, MethodNotAllowedException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to delete email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());

View File

@@ -22,6 +22,7 @@ import static com.google.gerrit.common.data.GlobalCapability.EMAIL_REVIEWERS;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.GENERATE_HTTP_PASSWORD;
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
import static com.google.gerrit.common.data.GlobalCapability.MODIFY_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS;
@@ -116,6 +117,7 @@ class GetCapabilities implements RestReadView<AccountResource> {
have.put(FLUSH_CACHES, cc.canFlushCaches());
have.put(GENERATE_HTTP_PASSWORD, cc.canGenerateHttpPassword());
have.put(KILL_TASK, cc.canKillTask());
have.put(MODIFY_ACCOUNT, cc.canModifyAccount());
have.put(RUN_GC, cc.canRunGC());
have.put(STREAM_EVENTS, cc.canStreamEvents());
have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts());

View File

@@ -40,7 +40,7 @@ public class GetEmails implements RestReadView<AccountResource> {
public List<EmailInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to list email addresses");
}

View File

@@ -45,7 +45,7 @@ public class GetSshKeys implements RestReadView<AccountResource> {
public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException,
OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to get SSH keys");
}
return apply(rsrc.getUser());

View File

@@ -29,7 +29,7 @@ import com.google.inject.Singleton;
import java.util.Collections;
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class PutActive implements RestModifyView<AccountResource, Input> {
public static class Input {

View File

@@ -64,7 +64,7 @@ public class PutName implements RestModifyView<AccountResource, Input> {
throws AuthException, MethodNotAllowedException,
ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to change name");
}
return apply(rsrc.getUser(), input);

View File

@@ -52,7 +52,7 @@ public class PutPreferred implements
public Response<String> apply(AccountResource.Email rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("not allowed to set preferred email address");
}
return apply(rsrc.getUser(), rsrc.getEmail());

View File

@@ -68,8 +68,8 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, Input
public DiffPreferencesInfo apply(AccountResource rsrc, Input input)
throws AuthException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("restricted to administrator");
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("restricted to members of Modify Accounts");
}
if (input == null) {
input = new Input();

View File

@@ -95,8 +95,8 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> {
throws AuthException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("restricted to administrator");
&& !self.get().getCapabilities().canModifyAccount()) {
throw new AuthException("restricted to members of Modify Accounts");
}
if (i == null) {
i = new Input();

View File

@@ -55,7 +55,7 @@ public class SshKeys implements
public AccountResource.SshKey parse(AccountResource rsrc, IdString id)
throws ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
&& !self.get().getCapabilities().canModifyAccount()) {
throw new ResourceNotFoundException();
}
return parse(rsrc.getUser(), id);

View File

@@ -31,6 +31,7 @@ public class CapabilityConstants extends TranslationBundle {
public String flushCaches;
public String generateHttpPassword;
public String killTask;
public String modifyAccount;
public String priority;
public String queryLimit;
public String runAs;

View File

@@ -7,6 +7,7 @@ emailReviewers = Email Reviewers
flushCaches = Flush Caches
generateHttpPassword = Generate HTTP Password
killTask = Kill Task
modifyAccount = Modify Account
priority = Priority
queryLimit = Query Limit
runAs = Run As

View File

@@ -14,7 +14,9 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -55,6 +57,7 @@ import java.util.List;
/** Set a user's account settings. **/
@CommandMetaData(name = "set-account", description = "Change an account's settings")
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
final class SetAccountCommand extends BaseCommand {
@Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id")
@@ -84,9 +87,6 @@ final class SetAccountCommand extends BaseCommand {
@Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account")
private String httpPassword;
@Inject
private IdentifiedUser currentUser;
@Inject
private IdentifiedUser.GenericFactory genericUserFactory;
@@ -128,13 +128,6 @@ final class SetAccountCommand extends BaseCommand {
startThread(new CommandRunnable() {
@Override
public void run() throws Exception {
if (!currentUser.getCapabilities().canAdministrateServer()) {
String msg =
String.format(
"fatal: %s does not have \"Administrator\" capability.",
currentUser.getUserName());
throw new UnloggedFailure(1, msg);
}
parseCommandLine();
validate();
setAccount();