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
					David Ostrovsky
				
			
				
					committed by
					
						 David Pursehouse
						David Pursehouse
					
				
			
			
				
	
			
			
			 David Pursehouse
						David Pursehouse
					
				
			
						parent
						
							e715d2484a
						
					
				
				
					commit
					aa49e277a3
				
			| @@ -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 | a replication task or a user initiated task such as an upload-pack or | ||||||
| receive-pack. | 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]] | [[capability_priority]] | ||||||
| === Priority | === Priority | ||||||
|   | |||||||
| @@ -21,7 +21,15 @@ It also allows managing email addresses, which bypasses the | |||||||
| verification step we force within the UI. | verification step we force within the UI. | ||||||
|  |  | ||||||
| == ACCESS | == 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 | == SCRIPTING | ||||||
| This command is intended to be used in scripts. | This command is intended to be used in scripts. | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ public class GlobalCapability { | |||||||
|   /** Can create any account on the server. */ |   /** Can create any account on the server. */ | ||||||
|   public static final String CREATE_ACCOUNT = "createAccount"; |   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. */ |   /** Can create any group on the server. */ | ||||||
|   public static final String CREATE_GROUP = "createGroup"; |   public static final String CREATE_GROUP = "createGroup"; | ||||||
|  |  | ||||||
| @@ -108,6 +111,7 @@ public class GlobalCapability { | |||||||
|     NAMES_ALL.add(FLUSH_CACHES); |     NAMES_ALL.add(FLUSH_CACHES); | ||||||
|     NAMES_ALL.add(GENERATE_HTTP_PASSWORD); |     NAMES_ALL.add(GENERATE_HTTP_PASSWORD); | ||||||
|     NAMES_ALL.add(KILL_TASK); |     NAMES_ALL.add(KILL_TASK); | ||||||
|  |     NAMES_ALL.add(MODIFY_ACCOUNT); | ||||||
|     NAMES_ALL.add(PRIORITY); |     NAMES_ALL.add(PRIORITY); | ||||||
|     NAMES_ALL.add(QUERY_LIMIT); |     NAMES_ALL.add(QUERY_LIMIT); | ||||||
|     NAMES_ALL.add(RUN_AS); |     NAMES_ALL.add(RUN_AS); | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ public class AddSshKey implements RestModifyView<AccountResource, Input> { | |||||||
|   public Response<SshKeyInfo> apply(AccountResource rsrc, Input input) |   public Response<SshKeyInfo> apply(AccountResource rsrc, Input input) | ||||||
|       throws AuthException, BadRequestException, OrmException, IOException { |       throws AuthException, BadRequestException, OrmException, IOException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to add SSH keys"); |       throw new AuthException("not allowed to add SSH keys"); | ||||||
|     } |     } | ||||||
|     return apply(rsrc.getUser(), input); |     return apply(rsrc.getUser(), input); | ||||||
|   | |||||||
| @@ -110,6 +110,12 @@ public class CapabilityControl { | |||||||
|       || canAdministrateServer(); |       || 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. */ |   /** @return true if the user can view all accounts. */ | ||||||
|   public boolean canViewAllAccounts() { |   public boolean canViewAllAccounts() { | ||||||
|     return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS) |     return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS) | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> { | |||||||
|       ResourceNotFoundException, OrmException, EmailException, |       ResourceNotFoundException, OrmException, EmailException, | ||||||
|       MethodNotAllowedException { |       MethodNotAllowedException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to add email address"); |       throw new AuthException("not allowed to add email address"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -98,8 +98,8 @@ public class CreateEmail implements RestModifyView<AccountResource, Input> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (input.noConfirmation |     if (input.noConfirmation | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("must be administrator to use no_confirmation"); |       throw new AuthException("not allowed to use no_confirmation"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return apply(rsrc.getUser(), input); |     return apply(rsrc.getUser(), input); | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ import com.google.inject.Singleton; | |||||||
|  |  | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  |  | ||||||
| @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) | @RequiresCapability(GlobalCapability.MODIFY_ACCOUNT) | ||||||
| @Singleton | @Singleton | ||||||
| public class DeleteActive implements RestModifyView<AccountResource, Input> { | public class DeleteActive implements RestModifyView<AccountResource, Input> { | ||||||
|   public static class Input { |   public static class Input { | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> | |||||||
|       throws AuthException, ResourceNotFoundException, |       throws AuthException, ResourceNotFoundException, | ||||||
|       ResourceConflictException, MethodNotAllowedException, OrmException { |       ResourceConflictException, MethodNotAllowedException, OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to delete email address"); |       throw new AuthException("not allowed to delete email address"); | ||||||
|     } |     } | ||||||
|     return apply(rsrc.getUser(), rsrc.getEmail()); |     return apply(rsrc.getUser(), rsrc.getEmail()); | ||||||
|   | |||||||
| @@ -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.FLUSH_CACHES; | ||||||
| import static com.google.gerrit.common.data.GlobalCapability.GENERATE_HTTP_PASSWORD; | 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.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.PRIORITY; | ||||||
| import static com.google.gerrit.common.data.GlobalCapability.RUN_GC; | import static com.google.gerrit.common.data.GlobalCapability.RUN_GC; | ||||||
| import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS; | 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(FLUSH_CACHES, cc.canFlushCaches()); | ||||||
|     have.put(GENERATE_HTTP_PASSWORD, cc.canGenerateHttpPassword()); |     have.put(GENERATE_HTTP_PASSWORD, cc.canGenerateHttpPassword()); | ||||||
|     have.put(KILL_TASK, cc.canKillTask()); |     have.put(KILL_TASK, cc.canKillTask()); | ||||||
|  |     have.put(MODIFY_ACCOUNT, cc.canModifyAccount()); | ||||||
|     have.put(RUN_GC, cc.canRunGC()); |     have.put(RUN_GC, cc.canRunGC()); | ||||||
|     have.put(STREAM_EVENTS, cc.canStreamEvents()); |     have.put(STREAM_EVENTS, cc.canStreamEvents()); | ||||||
|     have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts()); |     have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts()); | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ public class GetEmails implements RestReadView<AccountResource> { | |||||||
|   public List<EmailInfo> apply(AccountResource rsrc) throws AuthException, |   public List<EmailInfo> apply(AccountResource rsrc) throws AuthException, | ||||||
|       OrmException { |       OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to list email addresses"); |       throw new AuthException("not allowed to list email addresses"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ public class GetSshKeys implements RestReadView<AccountResource> { | |||||||
|   public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException, |   public List<SshKeyInfo> apply(AccountResource rsrc) throws AuthException, | ||||||
|       OrmException { |       OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to get SSH keys"); |       throw new AuthException("not allowed to get SSH keys"); | ||||||
|     } |     } | ||||||
|     return apply(rsrc.getUser()); |     return apply(rsrc.getUser()); | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ import com.google.inject.Singleton; | |||||||
|  |  | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  |  | ||||||
| @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) | @RequiresCapability(GlobalCapability.MODIFY_ACCOUNT) | ||||||
| @Singleton | @Singleton | ||||||
| public class PutActive implements RestModifyView<AccountResource, Input> { | public class PutActive implements RestModifyView<AccountResource, Input> { | ||||||
|   public static class Input { |   public static class Input { | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ public class PutName implements RestModifyView<AccountResource, Input> { | |||||||
|       throws AuthException, MethodNotAllowedException, |       throws AuthException, MethodNotAllowedException, | ||||||
|       ResourceNotFoundException, OrmException { |       ResourceNotFoundException, OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to change name"); |       throw new AuthException("not allowed to change name"); | ||||||
|     } |     } | ||||||
|     return apply(rsrc.getUser(), input); |     return apply(rsrc.getUser(), input); | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ public class PutPreferred implements | |||||||
|   public Response<String> apply(AccountResource.Email rsrc, Input input) |   public Response<String> apply(AccountResource.Email rsrc, Input input) | ||||||
|       throws AuthException, ResourceNotFoundException, OrmException { |       throws AuthException, ResourceNotFoundException, OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("not allowed to set preferred email address"); |       throw new AuthException("not allowed to set preferred email address"); | ||||||
|     } |     } | ||||||
|     return apply(rsrc.getUser(), rsrc.getEmail()); |     return apply(rsrc.getUser(), rsrc.getEmail()); | ||||||
|   | |||||||
| @@ -68,8 +68,8 @@ public class SetDiffPreferences implements RestModifyView<AccountResource, Input | |||||||
|   public DiffPreferencesInfo apply(AccountResource rsrc, Input input) |   public DiffPreferencesInfo apply(AccountResource rsrc, Input input) | ||||||
|       throws AuthException, OrmException { |       throws AuthException, OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("restricted to administrator"); |       throw new AuthException("restricted to members of Modify Accounts"); | ||||||
|     } |     } | ||||||
|     if (input == null) { |     if (input == null) { | ||||||
|       input = new Input(); |       input = new Input(); | ||||||
|   | |||||||
| @@ -95,8 +95,8 @@ public class SetPreferences implements RestModifyView<AccountResource, Input> { | |||||||
|       throws AuthException, ResourceNotFoundException, OrmException, |       throws AuthException, ResourceNotFoundException, OrmException, | ||||||
|       IOException, ConfigInvalidException { |       IOException, ConfigInvalidException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new AuthException("restricted to administrator"); |       throw new AuthException("restricted to members of Modify Accounts"); | ||||||
|     } |     } | ||||||
|     if (i == null) { |     if (i == null) { | ||||||
|       i = new Input(); |       i = new Input(); | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ public class SshKeys implements | |||||||
|   public AccountResource.SshKey parse(AccountResource rsrc, IdString id) |   public AccountResource.SshKey parse(AccountResource rsrc, IdString id) | ||||||
|       throws ResourceNotFoundException, OrmException { |       throws ResourceNotFoundException, OrmException { | ||||||
|     if (self.get() != rsrc.getUser() |     if (self.get() != rsrc.getUser() | ||||||
|         && !self.get().getCapabilities().canAdministrateServer()) { |         && !self.get().getCapabilities().canModifyAccount()) { | ||||||
|       throw new ResourceNotFoundException(); |       throw new ResourceNotFoundException(); | ||||||
|     } |     } | ||||||
|     return parse(rsrc.getUser(), id); |     return parse(rsrc.getUser(), id); | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ public class CapabilityConstants extends TranslationBundle { | |||||||
|   public String flushCaches; |   public String flushCaches; | ||||||
|   public String generateHttpPassword; |   public String generateHttpPassword; | ||||||
|   public String killTask; |   public String killTask; | ||||||
|  |   public String modifyAccount; | ||||||
|   public String priority; |   public String priority; | ||||||
|   public String queryLimit; |   public String queryLimit; | ||||||
|   public String runAs; |   public String runAs; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ emailReviewers = Email Reviewers | |||||||
| flushCaches = Flush Caches | flushCaches = Flush Caches | ||||||
| generateHttpPassword = Generate HTTP Password | generateHttpPassword = Generate HTTP Password | ||||||
| killTask = Kill Task | killTask = Kill Task | ||||||
|  | modifyAccount = Modify Account | ||||||
| priority = Priority | priority = Priority | ||||||
| queryLimit = Query Limit | queryLimit = Query Limit | ||||||
| runAs = Run As | runAs = Run As | ||||||
|   | |||||||
| @@ -14,7 +14,9 @@ | |||||||
|  |  | ||||||
| package com.google.gerrit.sshd.commands; | package com.google.gerrit.sshd.commands; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.common.data.GlobalCapability; | ||||||
| import com.google.gerrit.common.errors.EmailException; | 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.RawInput; | ||||||
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; | import com.google.gerrit.extensions.restapi.ResourceNotFoundException; | ||||||
| import com.google.gerrit.extensions.restapi.RestApiException; | import com.google.gerrit.extensions.restapi.RestApiException; | ||||||
| @@ -55,6 +57,7 @@ import java.util.List; | |||||||
|  |  | ||||||
| /** Set a user's account settings. **/ | /** Set a user's account settings. **/ | ||||||
| @CommandMetaData(name = "set-account", description = "Change an account's settings") | @CommandMetaData(name = "set-account", description = "Change an account's settings") | ||||||
|  | @RequiresCapability(GlobalCapability.MODIFY_ACCOUNT) | ||||||
| final class SetAccountCommand extends BaseCommand { | final class SetAccountCommand extends BaseCommand { | ||||||
|  |  | ||||||
|   @Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id") |   @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") |   @Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account") | ||||||
|   private String httpPassword; |   private String httpPassword; | ||||||
|  |  | ||||||
|   @Inject |  | ||||||
|   private IdentifiedUser currentUser; |  | ||||||
|  |  | ||||||
|   @Inject |   @Inject | ||||||
|   private IdentifiedUser.GenericFactory genericUserFactory; |   private IdentifiedUser.GenericFactory genericUserFactory; | ||||||
|  |  | ||||||
| @@ -128,13 +128,6 @@ final class SetAccountCommand extends BaseCommand { | |||||||
|     startThread(new CommandRunnable() { |     startThread(new CommandRunnable() { | ||||||
|       @Override |       @Override | ||||||
|       public void run() throws Exception { |       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(); |         parseCommandLine(); | ||||||
|         validate(); |         validate(); | ||||||
|         setAccount(); |         setAccount(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user