Project permissions capabilities Added show-repo-account-access command added show-account-access command Cleaned up documentation for show-account command reformatted some code to better meet Gerrit's standards Tweaked error handling, switched to @RequiresCapability to ensure calling user is an Administrator Change-Id: I5c1e53ddeebea18f2ee9d04b9d25d964dbb1ee6d
diff --git a/src/main/java/com/google/gerrit/plugins/AdminConsoleCommandModule.java b/src/main/java/com/google/gerrit/plugins/AdminConsoleCommandModule.java index be523e4..9a781da 100644 --- a/src/main/java/com/google/gerrit/plugins/AdminConsoleCommandModule.java +++ b/src/main/java/com/google/gerrit/plugins/AdminConsoleCommandModule.java
@@ -21,5 +21,7 @@ protected void configureCommands() { command(ShowAccountCommand.class); alias("show-account", ShowAccountCommand.class); + alias("show-repo-account-access", ShowRepoAccountAccessCommand.class); + alias("show-repo-access", ShowRepoAccessCommand.class); } }
diff --git a/src/main/java/com/google/gerrit/plugins/ShowAccountCommand.java b/src/main/java/com/google/gerrit/plugins/ShowAccountCommand.java index 2d35ab5..2c80d51 100644 --- a/src/main/java/com/google/gerrit/plugins/ShowAccountCommand.java +++ b/src/main/java/com/google/gerrit/plugins/ShowAccountCommand.java
@@ -13,6 +13,7 @@ // limitations under the License. package com.google.gerrit.plugins; + import java.io.IOException; import java.util.Collections; import java.util.Comparator; @@ -23,12 +24,13 @@ import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account.Id; import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountSshKey; import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountResolver; import com.google.gerrit.server.account.AccountResource; @@ -36,12 +38,13 @@ import com.google.gerrit.server.group.GroupJson.GroupInfo; import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.SshCommand; -import com.google.gwtorm.server.ResultSet; +import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; import com.google.inject.Provider; -@CommandMetaData(name="show-account", descr="Displays user information") +@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) +@CommandMetaData(name = "show-account", descr = "Displays user information") public final class ShowAccountCommand extends SshCommand { @Argument(usage = "User information to find: LastName,\\ Firstname, email@address.com, account id or an user name. Be sure to double-escape spaces, for example: \"show-account Last,\\\\ First\"") @@ -56,16 +59,17 @@ @Option(name = "--show-keys", usage = "show user's public keys?") private boolean showKeys = false; - final CurrentUser currentUser; final AccountResolver accountResolver; private final SchemaFactory<ReviewDb> schema; private final Provider<GetGroups> accountGetGroups; private final IdentifiedUser.GenericFactory userFactory; @Inject - ShowAccountCommand(final CurrentUser cu, AccountResolver ar, final Provider<GetGroups> accountGetGroups, final IdentifiedUser.GenericFactory userFactory, SchemaFactory<ReviewDb> schema) - throws ConfigInvalidException, IOException { - currentUser = cu; + ShowAccountCommand(AccountResolver ar, + final Provider<GetGroups> accountGetGroups, + final IdentifiedUser.GenericFactory userFactory, + SchemaFactory<ReviewDb> schema) throws ConfigInvalidException, + IOException { accountResolver = ar; this.accountGetGroups = accountGetGroups; this.userFactory = userFactory; @@ -73,30 +77,29 @@ } @Override - public void run() throws UnloggedFailure, Failure, Exception { + public void run() throws UnloggedFailure, OrmException { Account account; - if (!currentUser.getCapabilities().canAdministrateServer()) { - stdout.println("You must be a Gerrit Administrator to run this command. Goodbye"); - return; - } if (name.isEmpty()) { - stdout.print("You need to tell me who to find: LastName,\\ Firstname, email@address.com, account id or an user name. Be sure to double-escape spaces, for example: \"show-account Last,\\\\ First\""); - return; + throw new UnloggedFailure(1, + "You need to tell me who to find: LastName,\\\\ Firstname, email@address.com, account id or an user name. " + + "Be sure to double-escape spaces, for example: \"show-account Last,\\\\ First\""); } Set<Id> idList = accountResolver.findAll(name); if (idList.isEmpty()) { - stdout.println("No accounts found for your query: \"" + name + "\""); - stdout.println("Tip: Try double-escaping spaces, for example: \"show-account Last,\\\\ First\""); - return; - } - else { - stdout.println("Found " + idList.size() + " result" + (idList.size() > 1 ? "s" : "") + ": for query: \"" + name + "\""); + throw new UnloggedFailure(1, + "No accounts found for your query: \"" + + name + + "\"" + + " Tip: Try double-escaping spaces, for example: \"show-account Last,\\\\ First\""); + } else { + stdout.println("Found " + idList.size() + " result" + + (idList.size() > 1 ? "s" : "") + ": for query: \"" + name + "\""); stdout.println(); } - for (Id id: idList) { + for (Id id : idList) { account = accountResolver.find(id.toString()); stdout.println("Full name: " + account.getFullName()); stdout.println("Account Id: " + id.toString()); @@ -109,26 +112,28 @@ stdout.println(""); stdout.println("External Ids:"); - stdout.println(String.format("%-50s %s", "Email Address:", "External Id:")); - for (AccountExternalId accountExternalId : db.accountExternalIds().byAccount(account.getId())) { + stdout.println(String + .format("%-50s %s", "Email Address:", "External Id:")); + for (AccountExternalId accountExternalId : db.accountExternalIds() + .byAccount(account.getId())) { stdout.println(String.format("%-50s %s", - (accountExternalId.getEmailAddress() == null ? "" : accountExternalId.getEmailAddress()), - accountExternalId.getExternalId())); + (accountExternalId.getEmailAddress() == null ? "" + : accountExternalId.getEmailAddress()), accountExternalId + .getExternalId())); } if (showKeys) { stdout.println(""); stdout.println("Public Keys:"); - List<AccountSshKey> sshKeys = db.accountSshKeys().byAccount(account.getId()).toList(); - if (sshKeys == null || sshKeys.isEmpty()){ + List<AccountSshKey> sshKeys = + db.accountSshKeys().byAccount(account.getId()).toList(); + if (sshKeys == null || sshKeys.isEmpty()) { stdout.println("None"); - } - else{ + } else { stdout.println(String.format("%-9s %s", "Status:", "Key:")); - for (AccountSshKey sshKey : sshKeys ) { - stdout.println(String.format("%-9s %s", - (sshKey.isValid() ? "Active" : "Inactive"), - sshKey.getSshPublicKey())); + for (AccountSshKey sshKey : sshKeys) { + stdout.println(String.format("%-9s %s", (sshKey.isValid() + ? "Active" : "Inactive"), sshKey.getSshPublicKey())); } } } @@ -137,12 +142,15 @@ if (showGroups) { stdout.println(); - stdout.println("Member of groups" + (filterGroups == null ? "" : " (Filtering on \"" + filterGroups + "\")") + ":"); - List<GroupInfo> groupInfos = accountGetGroups.get().apply( - new AccountResource(userFactory.create(id))); + stdout.println("Member of groups" + + (filterGroups == null ? "" : " (Filtering on \"" + filterGroups + + "\")") + ":"); + List<GroupInfo> groupInfos = + accountGetGroups.get().apply( + new AccountResource(userFactory.create(id))); Collections.sort(groupInfos, new CustomComparator()); - for (GroupInfo groupInfo: groupInfos) { + for (GroupInfo groupInfo : groupInfos) { if (null == filterGroups) { stdout.println(groupInfo.name); } @@ -158,4 +166,4 @@ return o1.name.compareTo(o2.name); } } -} \ No newline at end of file +}
diff --git a/src/main/java/com/google/gerrit/plugins/ShowRepoAccessCommand.java b/src/main/java/com/google/gerrit/plugins/ShowRepoAccessCommand.java new file mode 100644 index 0000000..cdf325b --- /dev/null +++ b/src/main/java/com/google/gerrit/plugins/ShowRepoAccessCommand.java
@@ -0,0 +1,106 @@ +// Copyright (C) 2012 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.plugins; + +import java.io.IOException; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.sshd.CommandMetaData; +import com.google.gerrit.sshd.SshCommand; +import com.google.inject.Inject; + +@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) +@CommandMetaData(name = "show-repo-access", descr = "Displays access on a specific repository") +public final class ShowRepoAccessCommand extends SshCommand { + + @Argument(usage = "project to show access for?") + private String projectName = ""; + + @Option(name = "-w", usage = "display without line width truncation") + private boolean wide; + + @Inject + ShowRepoAccessCommand(final MetaDataUpdate.Server metaDataUpdateFactory) + throws ConfigInvalidException, IOException { + this.metaDataUpdateFactory = metaDataUpdateFactory; + } + + private final MetaDataUpdate.Server metaDataUpdateFactory; + + private int columns = 80; + private int permissionGroupWidth; + + @Override + public void run() throws UnloggedFailure, Failure, IOException, + ConfigInvalidException { + // space indented Strings to be used as format for String.format() later + String sectionNameFormatter = " %-25s\n"; + String ruleNameFormatter = " %-15s\n "; + String permissionNameFormatter = " %5s %9s %s\n"; + + if (projectName.isEmpty()) { + throw new UnloggedFailure(1, "Please specify a project to show access for"); + } + final Project.NameKey nameKey = new Project.NameKey(projectName); + + permissionGroupWidth = wide ? Integer.MAX_VALUE : columns - 9 - 5 - 9; + + ProjectConfig config; + try { + MetaDataUpdate md = metaDataUpdateFactory.create(nameKey); + config = ProjectConfig.read(md); + for (AccessSection accessSection : config.getAccessSections()) { + + stdout.print((String.format(sectionNameFormatter, accessSection + .getName().toString()))); + + for (Permission permission : accessSection.getPermissions()) { + + for (PermissionRule rule : permission.getRules()) { + stdout + .print(String.format(ruleNameFormatter, permission.getName())); + stdout.print(String.format(permissionNameFormatter, + (rule.getMin() != rule.getMax()) ? "" + rule.getMin() + " " + + rule.getMax() : rule.getAction(), + (permission.getExclusiveGroup() ? "EXCLUSIVE" : ""), + format(rule.getGroup().getName()))); + } + } + } + } catch (RepositoryNotFoundException e) { + throw new UnloggedFailure(1, "Repository not found"); + } + } + + private String format(final String s) { + if (s.length() < permissionGroupWidth) { + return s; + } else { + return s.substring(0, permissionGroupWidth); + } + } +}
diff --git a/src/main/java/com/google/gerrit/plugins/ShowRepoAccountAccessCommand.java b/src/main/java/com/google/gerrit/plugins/ShowRepoAccountAccessCommand.java new file mode 100644 index 0000000..cf2caeb --- /dev/null +++ b/src/main/java/com/google/gerrit/plugins/ShowRepoAccountAccessCommand.java
@@ -0,0 +1,184 @@ +// Copyright (C) 2012 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.plugins; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.Account.Id; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.AccountResolver; +import com.google.gerrit.server.account.AccountResource; +import com.google.gerrit.server.account.GetGroups; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.group.GroupJson.GroupInfo; +import com.google.gerrit.sshd.CommandMetaData; +import com.google.gerrit.sshd.SshCommand; +import com.google.inject.Inject; +import com.google.inject.Provider; + +@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) +@CommandMetaData(name = "show-repo-account-access", descr = "Displays user's access on a specific repository") +public final class ShowRepoAccountAccessCommand extends SshCommand { + + @Argument(usage = "project to show access for?") + private String projectName = ""; + + @Option(name = "--user", usage = "User information to find: LastName,\\ Firstname, email@address.com, account id or an user name. " + + "Be sure to double-escape spaces, for example: \"show-repo-account-access All-Projects --user Last,\\\\ First\"") + private String name = ""; + + @Option(name = "-w", usage = "display without line width truncation") + private boolean wide; + + @Inject + ShowRepoAccountAccessCommand( + final MetaDataUpdate.Server metaDataUpdateFactory, + final Provider<GetGroups> accountGetGroups, + AccountResolver accountResolver, + final IdentifiedUser.GenericFactory userFactory) + throws ConfigInvalidException, IOException { + this.metaDataUpdateFactory = metaDataUpdateFactory; + this.accountGetGroups = accountGetGroups; + this.accountResolver = accountResolver; + this.userFactory = userFactory; + } + + private final MetaDataUpdate.Server metaDataUpdateFactory; + private final AccountResolver accountResolver; + private final Provider<GetGroups> accountGetGroups; + private final IdentifiedUser.GenericFactory userFactory; + private int columns = 80; + private int permissionGroupWidth; + + @Override + public void run() throws UnloggedFailure, Failure, Exception { + Account account; + String sectionNameFormatter = " %-25s\n"; + String ruleNameFormatter = " %-15s\n "; + String permissionNameFormatter = " %5s %9s %s\n"; + + Boolean userHasPermissionsInSection = false; + Boolean userHasPermissionsInProject = false; + + if (projectName.isEmpty()) { + throw new UnloggedFailure(1, "Please specify a project to show access for"); + } + + if (name.isEmpty()) { + throw new UnloggedFailure(1, + "You need to tell me who to find: LastName,\\\\ Firstname, email@address.com, account id or an user name. " + + "Be sure to double-escape spaces, for example: \"show-repo-account-access All-Projects --user Last,\\\\ First\""); + } + + Set<Id> idList = accountResolver.findAll(name); + if (idList.isEmpty()) { + throw new UnloggedFailure(1, + "No accounts found for your query: \"" + + name + + "\"" + + " Tip: Try double-escaping spaces, for example: \"--user Last,\\\\ First\""); + } + + final Project.NameKey nameKey = new Project.NameKey(projectName); + + try { + MetaDataUpdate md = metaDataUpdateFactory.create(nameKey); + ProjectConfig config; + config = ProjectConfig.read(md); + + permissionGroupWidth = wide ? Integer.MAX_VALUE : columns - 9 - 5 - 9; + + for (Id id : idList) { + userHasPermissionsInProject = false; + account = accountResolver.find(id.toString()); + stdout.println("Full name: " + account.getFullName()); + // Need to know what groups the user is in. This is not a great + // solution, but it does work. + List<GroupInfo> groupInfos = + accountGetGroups.get().apply( + new AccountResource(userFactory.create(id))); + HashSet<String> groupHash = new HashSet<String>(); + + for (GroupInfo groupInfo : groupInfos) { + groupHash.add(groupInfo.name); + } + + for (AccessSection accessSection : config.getAccessSections()) { + StringBuilder sb = new StringBuilder(); + sb.append((String.format(sectionNameFormatter, accessSection + .getName().toString()))); + // This is a solution to prevent displaying a section heading unless + // the user has permissions for it + // not the best solution, but I haven't been able to find + // "Is user a member of this group" based on the information I have + // in a more efficient manner yet. + userHasPermissionsInSection = false; + for (Permission permission : accessSection.getPermissions()) { + + for (PermissionRule rule : permission.getRules()) { + + if (groupHash.contains(rule.getGroup().getName())) { + sb.append(String.format(ruleNameFormatter, permission.getName())); + sb.append(String.format(permissionNameFormatter, + (rule.getMin() != rule.getMax()) ? "" + rule.getMin() + " " + + rule.getMax() : rule.getAction(), + (permission.getExclusiveGroup() ? "EXCLUSIVE" : ""), + format(rule.getGroup().getName()))); + userHasPermissionsInSection = true; + } + } + } + + if (userHasPermissionsInSection) { + stdout.print(sb.toString()); + + userHasPermissionsInProject = true; + } + } + + + if (!userHasPermissionsInProject) { + stdout.println(" No access found for this user on this repository"); + } + } + } catch (RepositoryNotFoundException e) { + throw new UnloggedFailure(1, "Repository not found"); + } + } + + private String format(final String s) { + if (s.length() < permissionGroupWidth) { + return s; + } else { + return s.substring(0, permissionGroupWidth); + } + } +}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index 16200d6..485a6a2 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -1,2 +1,3 @@ Plugin to provide administrator-only functionality, intended to simplify common administrator tasks. Currently providing user-level information. +Also providing access control information by project or project/account \ No newline at end of file
diff --git a/src/main/resources/Documentation/cmd-show-account.md b/src/main/resources/Documentation/cmd-show-account.md index 096b407..c1455ec 100644 --- a/src/main/resources/Documentation/cmd-show-account.md +++ b/src/main/resources/Documentation/cmd-show-account.md
@@ -1,13 +1,13 @@ -admin-console show-account +@PLUGIN@ show-account ================ NAME ---- -admin-console show-account show user account information +@PLUGIN@ show-account show user account information SYNOPSIS -------- -> ssh -p <port> <host> admin-console show-account +> ssh -p <port> <host> @PLUGIN@ show-account > [user] > [--show-groups] > [--filter-groups] [filter-string] @@ -53,20 +53,20 @@ Find a user named Foo -> $ ssh -p 29418 review.example.com admin-console show-user Foo +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-account Foo Find a user with email foo@bar.com -> $ ssh -p 29418 review.example.com admin-console show-user foo@bar.com +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-account foo@bar.com Find a user named Foo Bar -> $ ssh -p 29418 review.example.com admin-console show-user Bar,\\\\ Foo +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-account Bar,\\ Foo Find a user named Foo and show all groups the user is a member of -> $ ssh -p 29418 review.example.com admin-console show-user Foo --show-groups +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-account Foo --show-groups Find a user named Foo and show all groups containing "baz" that the user is a member of -> $ ssh -p 29418 review.example.com admin-console show-user Foo --show-groups --filter-groups baz +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-account Foo --show-groups --filter-groups baz
diff --git a/src/main/resources/Documentation/cmd-show-repo-access.md b/src/main/resources/Documentation/cmd-show-repo-access.md new file mode 100644 index 0000000..558ab8e --- /dev/null +++ b/src/main/resources/Documentation/cmd-show-repo-access.md
@@ -0,0 +1,48 @@ +@PLUGIN@ show-repo-access +================ + +NAME +---- +@PLUGIN@ show-repo-access repository access information + +SYNOPSIS +-------- +> ssh -p <port> <host> @PLUGIN@ show-repo-access +> [repository] +> [-w] + + +DESCRIPTION +----------- +Displays access for a specific repository. Does not interpret repository inheritance (currently - may change in the future, or be added as an option) +Note, see [cmd-show-account](cmd-show-account.html) for more information on the user search functionality provided here. + +OPTIONS +------- +repository +> Repository to show access for + +-w +> Display without line width truncation +--help + +-h +> Display usage information. + +ACCESS +------ +Gerrit Administrators only. + +SCRIPTING +--------- +This command is not intended to be used in scripts. + +EXAMPLES +-------- + +Find Access for All-Projects + +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-repo-access All-Projects + +Find Access for a repository named "my-project" +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-repo-access my-project \ No newline at end of file
diff --git a/src/main/resources/Documentation/cmd-show-repo-account-access.md b/src/main/resources/Documentation/cmd-show-repo-account-access.md new file mode 100644 index 0000000..e59913b --- /dev/null +++ b/src/main/resources/Documentation/cmd-show-repo-account-access.md
@@ -0,0 +1,52 @@ +@PLUGIN@ show-repo-account-access +================ + +NAME +---- +@PLUGIN@ show-repo-account-access show repository access by account + +SYNOPSIS +-------- +> ssh -p <port> <host> @PLUGIN@ show-repo-account-access +> [repository] +> [--user] [user] +> [-w] + + +DESCRIPTION +----------- +Displays access for a specific repository by user. Does not interpret repository inheritance (currently - may change in the future, or be added as an option) + +OPTIONS +------- +repository +> Repository to show access for + +--user +> User to look up: This can be in one of several formats: LastName,\\\\ FirstName, email\@address.com, account id or an user name. Be sure to double-escape spaces. Case-sensitive + +-w +> Display without line width truncation +--help + +-h +> Display usage information. + +ACCESS +------ +Gerrit Administrators only. + +SCRIPTING +--------- +This command is not intended to be used in scripts. + +EXAMPLES +-------- + +Find Access for All-Projects and a user named Foo + +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-repo-account-access All-Projects --user Foo + +Find Access for a repository named "my-project" and a user named Foo + +> $ ssh -p @SSH_PORT@ review.example.com @PLUGIN@ show-repo-access my-project --user Foo \ No newline at end of file