From 7f48514889463c931e3f8a759756d4606c1efc0a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 16 Jun 2011 13:49:42 -0700 Subject: [PATCH] Add fine-grained capabilities for administrative actions The Global Capabilities section in All-Projects can now be used to grant subcommands that are available over SSH and were previously restricted to only Administrators. Bug: issue 48 Bug: issue 742 Change-Id: I7d8a931b174915191817ff845f1f9a846181d709 --- Documentation/cmd-create-account.txt | 3 +- Documentation/cmd-create-group.txt | 3 +- Documentation/cmd-flush-caches.txt | 3 +- Documentation/cmd-kill.txt | 3 +- Documentation/cmd-replicate.txt | 3 +- Documentation/cmd-show-caches.txt | 3 +- Documentation/cmd-show-connections.txt | 3 +- Documentation/cmd-show-queue.txt | 7 +++- .../gerrit/common/data/GlobalCapability.java | 33 +++++++++++++++ .../gerrit/common/data/GroupAdminService.java | 4 +- .../google/gerrit/common/data/GroupList.java | 40 +++++++++++++++++++ .../errors/PermissionDeniedException.java | 24 +++++++++++ .../client/admin/AdminConstants.properties | 18 ++++++++- .../gerrit/client/admin/GroupListScreen.java | 21 +++++----- .../gerrit/httpd/rpc/account/CreateGroup.java | 10 ++++- .../rpc/account/GroupAdminServiceImpl.java | 23 ++++++----- .../server/account/CapabilityControl.java | 40 +++++++++++++++++++ .../com/google/gerrit/sshd/BaseCommand.java | 2 +- ...Account.java => CreateAccountCommand.java} | 11 +++-- ...eateGroup.java => CreateGroupCommand.java} | 18 ++++++--- .../sshd/commands/DefaultCommandModule.java | 8 ++-- ...AdminFlushCaches.java => FlushCaches.java} | 28 +++++++++++-- .../{AdminKill.java => KillCommand.java} | 17 ++++++-- .../sshd/commands/MasterCommandModule.java | 6 +-- .../{AdminReplicate.java => Replicate.java} | 17 ++++++-- .../{AdminShowCaches.java => ShowCaches.java} | 17 ++++++-- ...wConnections.java => ShowConnections.java} | 16 ++++++-- .../gerrit/sshd/commands/ShowQueue.java | 14 +++---- 28 files changed, 323 insertions(+), 72 deletions(-) create mode 100644 gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java create mode 100644 gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminCreateAccount.java => CreateAccountCommand.java} (94%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminCreateGroup.java => CreateGroupCommand.java} (86%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminFlushCaches.java => FlushCaches.java} (77%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminKill.java => KillCommand.java} (81%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminReplicate.java => Replicate.java} (84%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminShowCaches.java => ShowCaches.java} (91%) rename gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/{AdminShowConnections.java => ShowConnections.java} (92%) diff --git a/Documentation/cmd-create-account.txt b/Documentation/cmd-create-account.txt index ba6a03ddb0..31bc482627 100644 --- a/Documentation/cmd-create-account.txt +++ b/Documentation/cmd-create-account.txt @@ -27,7 +27,8 @@ created in Gerrit that do not exist in the underlying LDAP directory. 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 the 'Create Account' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt index d2f76efa07..1a6c168787 100644 --- a/Documentation/cmd-create-group.txt +++ b/Documentation/cmd-create-group.txt @@ -26,7 +26,8 @@ becomes a member of the newly created group. 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 the 'Create Group' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt index 9b5bab8024..f8882c8115 100644 --- a/Documentation/cmd-flush-caches.txt +++ b/Documentation/cmd-flush-caches.txt @@ -25,7 +25,8 @@ If no options are supplied, defaults to `--all`. ACCESS ------ -Caller must be a member of the privileged 'Administrators' group. +Caller must be a member of the privileged 'Administrators' group, +or have granted the 'Flush Caches' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-kill.txt b/Documentation/cmd-kill.txt index a76be84e60..57a98f780f 100644 --- a/Documentation/cmd-kill.txt +++ b/Documentation/cmd-kill.txt @@ -18,7 +18,8 @@ its next cancellation point (which is usually blocking IO). 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 the 'Kill Task' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-replicate.txt b/Documentation/cmd-replicate.txt index 02d8883cef..b4ffb0e1bc 100644 --- a/Documentation/cmd-replicate.txt +++ b/Documentation/cmd-replicate.txt @@ -52,7 +52,8 @@ just the affected project can update the mirrors. 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 the 'Start Replication' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt index 599885914a..997f9f629c 100644 --- a/Documentation/cmd-show-caches.txt +++ b/Documentation/cmd-show-caches.txt @@ -16,7 +16,8 @@ Display statistics about the size and hit ratio of in-memory caches. 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 the 'View Caches' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt index ad8dbfb6b8..16cd5b4676 100644 --- a/Documentation/cmd-show-connections.txt +++ b/Documentation/cmd-show-connections.txt @@ -18,7 +18,8 @@ an activity. 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 the 'View Connections' global capability. SCRIPTING --------- diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt index 947cfb0fcc..1e97f48e3b 100644 --- a/Documentation/cmd-show-queue.txt +++ b/Documentation/cmd-show-queue.txt @@ -24,7 +24,12 @@ in these states. ACCESS ------ -Caller must be a member of the privileged 'Administrators' group. +End-users may see a task in the queue only if they can also see +the project the task is associated with. Tasks operating on other +projects, or that do not have a specific project are hidden. + +Members of the group 'Administrators', or any group that has been +granted the 'View Queue' capability can see all queue entries. SCRIPTING --------- diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java index 0afdd042c3..053f4e924f 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java @@ -19,13 +19,46 @@ import java.util.List; /** Server wide capabilities. Represented as {@link Permission} objects. */ public class GlobalCapability { + /** Can create any group on the server. */ + public static final String CREATE_GROUP = "createGroup"; + + /** Can create any account on the server. */ + public static final String CREATE_ACCOUNT = "createAccount"; + + /** Can flush any cache except the active web_sessions cache. */ + public static final String FLUSH_CACHES = "flushCaches"; + + /** Can terminate any task using the kill command. */ + public static final String KILL_TASK = "killTask"; + + /** Maximum result limit per executed query. */ public static final String QUERY_LIMIT = "queryLimit"; + /** Forcefully restart replication to any configured destination. */ + public static final String START_REPLICATION = "startReplication"; + + /** Can view the server's current cache states. */ + public static final String VIEW_CACHES = "viewCaches"; + + /** Can view open connections to the server's SSH port. */ + public static final String VIEW_CONNECTIONS = "viewConnections"; + + /** Can view all pending tasks in the queue (not just the filtered set). */ + public static final String VIEW_QUEUE = "viewQueue"; + private static final List NAMES_LC; static { NAMES_LC = new ArrayList(); + NAMES_LC.add(CREATE_GROUP.toLowerCase()); + NAMES_LC.add(CREATE_ACCOUNT.toLowerCase()); + NAMES_LC.add(FLUSH_CACHES.toLowerCase()); + NAMES_LC.add(KILL_TASK.toLowerCase()); NAMES_LC.add(QUERY_LIMIT.toLowerCase()); + NAMES_LC.add(START_REPLICATION.toLowerCase()); + NAMES_LC.add(VIEW_CACHES.toLowerCase()); + NAMES_LC.add(VIEW_CONNECTIONS.toLowerCase()); + NAMES_LC.add(VIEW_QUEUE.toLowerCase()); } /** @return true if the name is recognized as a capability name. */ diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java index 6d4b8894f2..087f04762e 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java @@ -21,8 +21,8 @@ import com.google.gerrit.reviewdb.AccountGroupMember; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwtjsonrpc.client.RemoteJsonService; import com.google.gwtjsonrpc.client.RpcImpl; -import com.google.gwtjsonrpc.client.RpcImpl.Version; import com.google.gwtjsonrpc.client.VoidResult; +import com.google.gwtjsonrpc.client.RpcImpl.Version; import java.util.List; import java.util.Set; @@ -30,7 +30,7 @@ import java.util.Set; @RpcImpl(version = Version.V2_0) public interface GroupAdminService extends RemoteJsonService { @SignInRequired - void visibleGroups(AsyncCallback> callback); + void visibleGroups(AsyncCallback callback); @SignInRequired void createGroup(String newName, AsyncCallback callback); diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java new file mode 100644 index 0000000000..1f0612092d --- /dev/null +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java @@ -0,0 +1,40 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.common.data; + +import com.google.gerrit.reviewdb.AccountGroup; + +import java.util.List; + +public class GroupList { + protected List groups; + protected boolean canCreateGroup; + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public boolean isCanCreateGroup() { + return canCreateGroup; + } + + public void setCanCreateGroup(boolean set) { + canCreateGroup = set; + } +} diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java new file mode 100644 index 0000000000..76520aab94 --- /dev/null +++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/PermissionDeniedException.java @@ -0,0 +1,24 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.common.errors; + +/** Indicats the user cannot perform this task. */ +public class PermissionDeniedException extends Exception { + private static final long serialVersionUID = 1L; + + public PermissionDeniedException(String msg) { + super(msg); + } +} diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index 563570f610..0b424de3d5 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties @@ -119,8 +119,24 @@ errorsMustBeFixed = Errors must be fixed before committing changes. # Capability Names capabilityNames = \ - queryLimit + createAccount, \ + createGroup, \ + flushCaches, \ + killTask, \ + queryLimit, \ + startReplication, \ + viewCaches, \ + viewConnections, \ + viewQueue +createAccount = Create Account +createGroup = Create Group +flushCaches = Flush Caches +killTask = Kill Task queryLimit = Query Limit +startReplication = Start Replication +viewCaches = View Caches +viewConnections = View Connections +viewQueue = View Queue # Section Names sectionTypeReference = Reference: diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java index a6dbedc7b8..38cc43688c 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java @@ -22,6 +22,7 @@ import com.google.gerrit.client.ui.AccountScreen; import com.google.gerrit.client.ui.OnEditEnabler; import com.google.gerrit.client.ui.SmallHeading; import com.google.gerrit.common.PageLinks; +import com.google.gerrit.common.data.GroupList; import com.google.gerrit.reviewdb.AccountGroup; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; @@ -42,6 +43,7 @@ import java.util.List; public class GroupListScreen extends AccountScreen { private GroupTable groups; + private VerticalPanel addPanel; private NpTextBox addTxt; private Button addNew; @@ -49,10 +51,11 @@ public class GroupListScreen extends AccountScreen { protected void onLoad() { super.onLoad(); Util.GROUP_SVC - .visibleGroups(new ScreenLoadCallback>(this) { + .visibleGroups(new ScreenLoadCallback(this) { @Override - protected void preDisplay(final List result) { - groups.display(result); + protected void preDisplay(GroupList result) { + addPanel.setVisible(result.isCanCreateGroup()); + groups.display(result.getGroups()); groups.finishDisplay(); } }); @@ -66,9 +69,9 @@ public class GroupListScreen extends AccountScreen { groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS); add(groups); - final VerticalPanel fp = new VerticalPanel(); - fp.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel()); - fp.add(new SmallHeading(Util.C.headingCreateGroup())); + addPanel = new VerticalPanel(); + addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel()); + addPanel.add(new SmallHeading(Util.C.headingCreateGroup())); addTxt = new NpTextBox(); addTxt.setVisibleLength(60); @@ -80,7 +83,7 @@ public class GroupListScreen extends AccountScreen { } } }); - fp.add(addTxt); + addPanel.add(addTxt); addNew = new Button(Util.C.buttonCreateGroup()); addNew.setEnabled(false); @@ -109,8 +112,8 @@ public class GroupListScreen extends AccountScreen { groups.setRegisterKeys(true); } }); - fp.add(addNew); - add(fp); + addPanel.add(addNew); + add(addPanel); new OnEditEnabler(addNew, addTxt); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java index 874cc74c0e..fcfb5683ff 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java @@ -15,6 +15,7 @@ package com.google.gerrit.httpd.rpc.account; import com.google.gerrit.common.errors.NameAlreadyUsedException; +import com.google.gerrit.common.errors.PermissionDeniedException; import com.google.gerrit.httpd.rpc.Handler; import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.AccountGroup; @@ -44,7 +45,14 @@ class CreateGroup extends Handler { } @Override - public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException { + public AccountGroup.Id call() throws OrmException, NameAlreadyUsedException, + PermissionDeniedException { + if (!user.getCapabilities().canCreateGroup()) { + throw new PermissionDeniedException(String.format( + "%s does not have \"Create Group\" capability.", + user.getUserName())); + } + final PerformCreateGroup performCreateGroup = performCreateGroupFactory.create(); final Account.Id me = user.getAccountId(); return performCreateGroup.createGroup(groupName, null, false, null, Collections.singleton(me), null); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java index 578e86626e..1940944a06 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java @@ -16,6 +16,7 @@ package com.google.gerrit.httpd.rpc.account; import com.google.gerrit.common.data.GroupAdminService; import com.google.gerrit.common.data.GroupDetail; +import com.google.gerrit.common.data.GroupList; import com.google.gerrit.common.data.GroupOptions; import com.google.gerrit.common.errors.InactiveAccountException; import com.google.gerrit.common.errors.NameAlreadyUsedException; @@ -88,28 +89,32 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements this.groupDetailFactory = groupDetailFactory; } - public void visibleGroups(final AsyncCallback> callback) { - run(callback, new Action>() { - public List run(ReviewDb db) throws OrmException { + public void visibleGroups(final AsyncCallback callback) { + run(callback, new Action() { + public GroupList run(ReviewDb db) throws OrmException { final IdentifiedUser user = identifiedUser.get(); - final List result; + final List list; if (user.isAdministrator()) { - result = db.accountGroups().all().toList(); + list = db.accountGroups().all().toList(); } else { - result = new ArrayList(); + list = new ArrayList(); for(final AccountGroup group : db.accountGroups().all().toList()) { final GroupControl c = groupControlFactory.controlFor(group); if (c.isVisible()) { - result.add(c.getAccountGroup()); + list.add(c.getAccountGroup()); } } } - Collections.sort(result, new Comparator() { + Collections.sort(list, new Comparator() { public int compare(final AccountGroup a, final AccountGroup b) { return a.getName().compareTo(b.getName()); } }); - return result; + + GroupList res = new GroupList(); + res.setGroups(list); + res.setCanCreateGroup(user.getCapabilities().canCreateGroup()); + return res; } }); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java index 993b59bca6..f53fe7a1e3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java @@ -64,6 +64,46 @@ public class CapabilityControl { return user; } + /** @return true if the user can create an account for another user. */ + public boolean canCreateAccount() { + return canPerform(GlobalCapability.CREATE_ACCOUNT) || user.isAdministrator(); + } + + /** @return true if the user can create a group. */ + public boolean canCreateGroup() { + return canPerform(GlobalCapability.CREATE_GROUP) || user.isAdministrator(); + } + + /** @return true if the user can kill any running task. */ + public boolean canKillTask() { + return canPerform(GlobalCapability.KILL_TASK) || user.isAdministrator(); + } + + /** @return true if the user can view the server caches. */ + public boolean canViewCaches() { + return canPerform(GlobalCapability.VIEW_CACHES) || user.isAdministrator(); + } + + /** @return true if the user can flush the server's caches. */ + public boolean canFlushCaches() { + return canPerform(GlobalCapability.FLUSH_CACHES) || user.isAdministrator(); + } + + /** @return true if the user can view open connections. */ + public boolean canViewConnections() { + return canPerform(GlobalCapability.VIEW_CONNECTIONS) || user.isAdministrator(); + } + + /** @return true if the user can view the entire queue. */ + public boolean canViewQueue() { + return canPerform(GlobalCapability.VIEW_QUEUE) || user.isAdministrator(); + } + + /** @return true if the user can force replication to any configured destination. */ + public boolean canStartReplication() { + return canPerform(GlobalCapability.START_REPLICATION) || user.isAdministrator(); + } + /** True if the user has this permission. Works only for non labels. */ public boolean canPerform(String permissionName) { return !access(permissionName).isEmpty(); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java index 4ba3e86d72..715f8bc462 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java @@ -58,7 +58,7 @@ public abstract class BaseCommand implements Command { private static final int PRIVATE_STATUS = 1 << 30; static final int STATUS_CANCEL = PRIVATE_STATUS | 1; static final int STATUS_NOT_FOUND = PRIVATE_STATUS | 2; - static final int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3; + public static final int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3; @Option(name = "--help", usage = "display this help text", aliases = {"-h"}) private boolean help; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java similarity index 94% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java index 06e92ca8ac..b46a2f2423 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java @@ -26,7 +26,6 @@ import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountByEmailCache; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.ssh.SshKeyCache; -import com.google.gerrit.sshd.AdminCommand; import com.google.gerrit.sshd.BaseCommand; import com.google.gwtorm.client.OrmDuplicateKeyException; import com.google.gwtorm.client.OrmException; @@ -46,8 +45,7 @@ import java.util.HashSet; import java.util.List; /** Create a new user account. **/ -@AdminCommand -final class AdminCreateAccount extends BaseCommand { +final class CreateAccountCommand extends BaseCommand { @Option(name = "--group", aliases = {"-g"}, metaVar = "GROUP", usage = "groups to add account to") private List groups = new ArrayList(); @@ -83,6 +81,13 @@ final class AdminCreateAccount extends BaseCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canCreateAccount()) { + String msg = String.format( + "fatal: %s does not have \"Create Account\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); createAccount(); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java similarity index 86% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java index 8622741524..76eca2a29b 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateGroup.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java @@ -17,8 +17,8 @@ package com.google.gerrit.sshd.commands; import com.google.gerrit.common.errors.NameAlreadyUsedException; import com.google.gerrit.reviewdb.Account; import com.google.gerrit.reviewdb.AccountGroup; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.PerformCreateGroup; -import com.google.gerrit.sshd.AdminCommand; import com.google.gerrit.sshd.BaseCommand; import com.google.gwtorm.client.OrmException; import com.google.inject.Inject; @@ -27,7 +27,6 @@ import org.apache.sshd.server.Environment; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import java.io.IOException; import java.util.HashSet; import java.util.Set; @@ -36,8 +35,7 @@ import java.util.Set; *

* Optionally, puts an initial set of user in the newly created group. */ -@AdminCommand -public class AdminCreateGroup extends BaseCommand { +final class CreateGroupCommand extends BaseCommand { @Option(name = "--owner", aliases = {"-o"}, metaVar = "GROUP", usage = "owning group, if not specified the group will be self-owning") private AccountGroup.Id ownerGroupId; @@ -64,14 +62,24 @@ public class AdminCreateGroup extends BaseCommand { initialGroups.add(id); } + @Inject + private IdentifiedUser currentUser; + @Inject private PerformCreateGroup.Factory performCreateGroupFactory; @Override - public void start(Environment env) throws IOException { + public void start(Environment env) { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canCreateGroup()) { + String msg = String.format( + "fatal: %s does not have \"Create Group\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); createGroup(); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java index 3511c71641..a7e3214c28 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java @@ -34,11 +34,11 @@ public class DefaultCommandModule extends CommandModule { // SlaveCommandModule. command(gerrit).toProvider(new DispatchCommandProvider(gerrit)); - command(gerrit, "flush-caches").to(AdminFlushCaches.class); + command(gerrit, "flush-caches").to(FlushCaches.class); command(gerrit, "ls-projects").to(ListProjects.class); command(gerrit, "query").to(Query.class); - command(gerrit, "show-caches").to(AdminShowCaches.class); - command(gerrit, "show-connections").to(AdminShowConnections.class); + command(gerrit, "show-caches").to(ShowCaches.class); + command(gerrit, "show-connections").to(ShowConnections.class); command(gerrit, "show-queue").to(ShowQueue.class); command(gerrit, "stream-events").to(StreamEvents.class); command(gerrit, "version").to(VersionCommand.class); @@ -48,7 +48,7 @@ public class DefaultCommandModule extends CommandModule { command(git, "upload-pack").to(Upload.class); command("ps").to(ShowQueue.class); - command("kill").to(AdminKill.class); + command("kill").to(KillCommand.class); command("scp").to(ScpCommand.class); // Honor the legacy hyphenated forms as aliases for the non-hyphenated forms diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java similarity index 77% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java index b3241ff285..a176c06ad7 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminFlushCaches.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java @@ -14,7 +14,9 @@ package com.google.gerrit.sshd.commands; -import com.google.gerrit.sshd.AdminCommand; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.sshd.BaseCommand; +import com.google.inject.Inject; import net.sf.ehcache.Ehcache; @@ -27,8 +29,9 @@ import java.util.List; import java.util.SortedSet; /** Causes the caches to purge all entries and reload. */ -@AdminCommand -final class AdminFlushCaches extends CacheCommand { +final class FlushCaches extends CacheCommand { + private static final String WEB_SESSIONS = "web_sessions"; + @Option(name = "--cache", usage = "flush named cache", metaVar = "NAME") private List caches = new ArrayList(); @@ -38,6 +41,9 @@ final class AdminFlushCaches extends CacheCommand { @Option(name = "--list", usage = "list available caches") private boolean list; + @Inject + IdentifiedUser currentUser; + private PrintWriter p; @Override @@ -45,6 +51,13 @@ final class AdminFlushCaches extends CacheCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canFlushCaches()) { + String msg = String.format( + "fatal: %s does not have \"Flush Caches\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); flush(); } @@ -52,6 +65,13 @@ final class AdminFlushCaches extends CacheCommand { } private void flush() throws Failure { + if (caches.contains(WEB_SESSIONS) && !currentUser.isAdministrator()) { + String msg = String.format( + "fatal: only site administrators can flush %s", + WEB_SESSIONS); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + p = toPrintWriter(err); if (list) { if (all || caches.size() > 0) { @@ -113,7 +133,7 @@ final class AdminFlushCaches extends CacheCommand { return true; } else if (all) { - if ("web_sessions".equals(cacheName)) { + if (WEB_SESSIONS.equals(cacheName)) { return false; } return true; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java similarity index 81% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java index 5550964246..69018afbb1 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminKill.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java @@ -14,10 +14,10 @@ package com.google.gerrit.sshd.commands; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.git.WorkQueue.Task; import com.google.gerrit.server.util.IdGenerator; -import com.google.gerrit.sshd.AdminCommand; import com.google.gerrit.sshd.BaseCommand; import com.google.inject.Inject; @@ -29,8 +29,10 @@ import java.util.HashSet; import java.util.Set; /** Kill a task in the work queue. */ -@AdminCommand -final class AdminKill extends BaseCommand { +final class KillCommand extends BaseCommand { + @Inject + private IdentifiedUser currentUser; + @Inject private WorkQueue workQueue; @@ -50,8 +52,15 @@ final class AdminKill extends BaseCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canKillTask()) { + String msg = String.format( + "fatal: %s does not have \"Kill Task\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); - AdminKill.this.commitMurder(); + KillCommand.this.commitMurder(); } }); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java index a3f0ceb985..c2f7608459 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java @@ -26,13 +26,13 @@ public class MasterCommandModule extends CommandModule { final CommandName gerrit = Commands.named("gerrit"); command(gerrit, "approve").to(ReviewCommand.class); - command(gerrit, "create-account").to(AdminCreateAccount.class); - command(gerrit, "create-group").to(AdminCreateGroup.class); + command(gerrit, "create-account").to(CreateAccountCommand.class); + command(gerrit, "create-group").to(CreateGroupCommand.class); command(gerrit, "create-project").to(CreateProject.class); command(gerrit, "gsql").to(AdminQueryShell.class); command(gerrit, "modify-reviewers").to(ModifyReviewersCommand.class); command(gerrit, "receive-pack").to(Receive.class); - command(gerrit, "replicate").to(AdminReplicate.class); + command(gerrit, "replicate").to(Replicate.class); command(gerrit, "set-project-parent").to(AdminSetParent.class); command(gerrit, "review").to(ReviewCommand.class); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java similarity index 84% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java index 8e2c74fffd..9dc8671339 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java @@ -15,10 +15,10 @@ package com.google.gerrit.sshd.commands; import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.PushAllProjectsOp; import com.google.gerrit.server.git.ReplicationQueue; import com.google.gerrit.server.project.ProjectCache; -import com.google.gerrit.sshd.AdminCommand; import com.google.gerrit.sshd.BaseCommand; import com.google.inject.Inject; @@ -31,8 +31,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; /** Force a project to replicate, again. */ -@AdminCommand -final class AdminReplicate extends BaseCommand { +final class Replicate extends BaseCommand { @Option(name = "--all", usage = "push all known projects") private boolean all; @@ -42,6 +41,9 @@ final class AdminReplicate extends BaseCommand { @Argument(index = 0, multiValued = true, metaVar = "PROJECT", usage = "project name") private List projectNames = new ArrayList(2); + @Inject + IdentifiedUser currentUser; + @Inject private PushAllProjectsOp.Factory pushAllOpFactory; @@ -56,8 +58,15 @@ final class AdminReplicate extends BaseCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canStartReplication()) { + String msg = String.format( + "fatal: %s does not have \"Start Replication\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); - AdminReplicate.this.schedule(); + Replicate.this.schedule(); } }); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java similarity index 91% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java index c27ad0db32..28676e5c0d 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowCaches.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java @@ -14,7 +14,9 @@ package com.google.gerrit.sshd.commands; -import com.google.gerrit.sshd.AdminCommand; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.sshd.BaseCommand; +import com.google.inject.Inject; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Statistics; @@ -26,8 +28,10 @@ import org.eclipse.jgit.storage.file.WindowCacheStatAccessor; import java.io.PrintWriter; /** Show the current cache states. */ -@AdminCommand -final class AdminShowCaches extends CacheCommand { +final class ShowCaches extends CacheCommand { + @Inject + IdentifiedUser currentUser; + private PrintWriter p; @Override @@ -35,6 +39,13 @@ final class AdminShowCaches extends CacheCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canViewCaches()) { + String msg = String.format( + "fatal: %s does not have \"View Caches\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); display(); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java similarity index 92% rename from gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java rename to gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java index b6c6119e74..a72ce90518 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminShowConnections.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java @@ -17,7 +17,6 @@ package com.google.gerrit.sshd.commands; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.util.IdGenerator; -import com.google.gerrit.sshd.AdminCommand; import com.google.gerrit.sshd.BaseCommand; import com.google.gerrit.sshd.SshDaemon; import com.google.gerrit.sshd.SshSession; @@ -41,13 +40,15 @@ import java.util.Date; import java.util.List; /** Show the current SSH connections. */ -@AdminCommand -final class AdminShowConnections extends BaseCommand { +final class ShowConnections extends BaseCommand { @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names") private boolean numeric; private PrintWriter p; + @Inject + IdentifiedUser currentUser; + @Inject private SshDaemon daemon; @@ -56,8 +57,15 @@ final class AdminShowConnections extends BaseCommand { startThread(new CommandRunnable() { @Override public void run() throws Exception { + if (!currentUser.getCapabilities().canViewConnections()) { + String msg = String.format( + "fatal: %s does not have \"View Connections\" capability.", + currentUser.getUserName()); + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + parseCommandLine(); - AdminShowConnections.this.display(); + ShowConnections.this.display(); } }); } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java index a196a3e329..80a6c11526 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java @@ -15,7 +15,7 @@ package com.google.gerrit.sshd.commands; import com.google.gerrit.reviewdb.Project; -import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.git.WorkQueue.ProjectTask; import com.google.gerrit.server.git.WorkQueue.Task; @@ -50,7 +50,7 @@ final class ShowQueue extends BaseCommand { private ProjectCache projectCache; @Inject - private CurrentUser userProvider; + private IdentifiedUser currentUser; private PrintWriter p; private int columns = 80; @@ -110,7 +110,7 @@ final class ShowQueue extends BaseCommand { int numberOfPendingTasks = 0; final long now = System.currentTimeMillis(); - final boolean isAdministrator = userProvider.isAdministrator(); + final boolean viewAll = currentUser.getCapabilities().canViewQueue(); for (final Task task : pending) { final long delay = task.getDelay(TimeUnit.MILLISECONDS); @@ -137,7 +137,7 @@ final class ShowQueue extends BaseCommand { Project.NameKey projectName = null; String remoteName = null; - if (!isAdministrator) { + if (!viewAll) { if (task instanceof ProjectTask) { projectName = ((ProjectTask)task).getProjectNameKey(); remoteName = ((ProjectTask)task).getRemoteName(); @@ -149,7 +149,7 @@ final class ShowQueue extends BaseCommand { e = projectCache.get(projectName); } - regularUserCanSee = e != null && e.controlFor(userProvider).isVisible(); + regularUserCanSee = e != null && e.controlFor(currentUser).isVisible(); if (regularUserCanSee) { numberOfPendingTasks++; @@ -157,7 +157,7 @@ final class ShowQueue extends BaseCommand { } // Shows information about tasks depending on the user rights - if (isAdministrator || (!hasCustomizedPrint && regularUserCanSee)) { + if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) { p.print(String.format("%8s %-12s %-8s %s\n", // id(task.getTaskId()), start, "", format(task))); } else if (regularUserCanSee) { @@ -174,7 +174,7 @@ final class ShowQueue extends BaseCommand { p.print("----------------------------------------------" + "--------------------------------\n"); - if (isAdministrator) { + if (viewAll) { numberOfPendingTasks = pending.size(); }