REST API /accounts/self/capabilities

This JSON end point can be used by a client to determine global
server capabilities for the user, for example common names like
"createProject" or "createGroup".

The q parameter can be used to filter the set of capabilities to be
smaller than the set recognized by this version of Gerrit. Filtering
may decrease response time by avoiding looking at every possible
alternative for the caller.

Most results are boolean, and a field is only present when its
value is true. queryLimit is a range and is presented as a nested
JSON object with min and max members.

$ curl --user $USER --digest 'http://localhost:8080/a/accounts/self/capabilities?format=JSON'
)]}'
{
  "administrateServer": true,
  "queryLimit": {
    "min": 0,
    "max": 500
  },
  "createAccount": true,
  "createGroup": true,
  "createProject": true,
  "killTask": true,
  "viewCaches": true,
  "flushCaches": true,
  "viewConnections": true,
  "viewQueue": true,
  "startReplication": true
}

Change-Id: I4052ade1da986ee409cc0d532e872211b4301f2d
This commit is contained in:
Shawn O. Pearce
2012-04-05 11:03:56 -07:00
parent 805a6f0b17
commit e016933846
7 changed files with 321 additions and 18 deletions

View File

@@ -55,8 +55,53 @@ save on network transfer time for larger responses.
Endpoints
---------
List Projects: /projects/
~~~~~~~~~~~~~~~~~~~~~~~~~
[[accounts_self_capabilities]]
/accounts/self/capabilities (Account Capabilities)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns the global capabilities (such as createProject or
createGroup) that are enabled for the calling user. This can be used
by UI tools to discover if administrative features are available
to the caller, so they can hide (or show) relevant UI actions.
----
GET /accounts/self/capabilities?format=JSON HTTP/1.0
)]}'
{
"queryLimit": {
"min": 0,
"max": 500
}
}
----
Administrator that has authenticated with digest authentication:
----
GET /a/accounts/self/capabilities?format=JSON HTTP/1.0
Authorization: Digest username="admin", realm="Gerrit Code Review", nonce="...
)]}'
{
"administrateServer": true,
"queryLimit": {
"min": 0,
"max": 500
},
"createAccount": true,
"createGroup": true,
"createProject": true,
"killTask": true,
"viewCaches": true,
"flushCaches": true,
"viewConnections": true,
"viewQueue": true,
"startReplication": true
}
----
[[projects]]
/projects/ (List Projects)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Lists the projects accessible by the caller. This is the same as
using the link:cmd-ls-projects.html[ls-projects] command over SSH,
and accepts the same options as query parameters.

View File

@@ -15,6 +15,8 @@
package com.google.gerrit.common.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/** Server wide capabilities. Represented as {@link Permission} objects. */
@@ -73,23 +75,34 @@ public class GlobalCapability {
/** Can view all pending tasks in the queue (not just the filtered set). */
public static final String VIEW_QUEUE = "viewQueue";
private static final List<String> NAMES_ALL;
private static final List<String> NAMES_LC;
static {
NAMES_LC = new ArrayList<String>();
NAMES_LC.add(ADMINISTRATE_SERVER.toLowerCase());
NAMES_LC.add(CREATE_ACCOUNT.toLowerCase());
NAMES_LC.add(CREATE_GROUP.toLowerCase());
NAMES_LC.add(CREATE_PROJECT.toLowerCase());
NAMES_LC.add(EMAIL_REVIEWERS.toLowerCase());
NAMES_LC.add(FLUSH_CACHES.toLowerCase());
NAMES_LC.add(KILL_TASK.toLowerCase());
NAMES_LC.add(PRIORITY.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());
NAMES_ALL = new ArrayList<String>();
NAMES_ALL.add(ADMINISTRATE_SERVER);
NAMES_ALL.add(CREATE_ACCOUNT);
NAMES_ALL.add(CREATE_GROUP);
NAMES_ALL.add(CREATE_PROJECT);
NAMES_ALL.add(EMAIL_REVIEWERS);
NAMES_ALL.add(FLUSH_CACHES);
NAMES_ALL.add(KILL_TASK);
NAMES_ALL.add(PRIORITY);
NAMES_ALL.add(QUERY_LIMIT);
NAMES_ALL.add(START_REPLICATION);
NAMES_ALL.add(VIEW_CACHES);
NAMES_ALL.add(VIEW_CONNECTIONS);
NAMES_ALL.add(VIEW_QUEUE);
NAMES_LC = new ArrayList<String>(NAMES_ALL.size());
for (String name : NAMES_ALL) {
NAMES_LC.add(name.toLowerCase());
}
}
/** @return all valid capability names. */
public static Collection<String> getAllNames() {
return Collections.unmodifiableList(NAMES_ALL);
}
/** @return true if the name is recognized as a capability name. */

View File

@@ -0,0 +1,36 @@
// 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.client.account;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwtjsonrpc.common.AsyncCallback;
/** Capabilities the caller has from {@code /accounts/self/capabilities}. */
public class AccountCapabilities extends JavaScriptObject {
public static void all(AsyncCallback<AccountCapabilities> cb, String... filter) {
RestApi api = new RestApi("/accounts/self/capabilities");
for (String name : filter) {
api.addParameter("q", name);
}
api.send(cb);
}
protected AccountCapabilities() {
}
public final native boolean canPerform(String name)
/*-{ return this[name] ? true : false; }-*/;
}

View File

@@ -14,8 +14,11 @@
package com.google.gerrit.client.admin;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountCapabilities;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.AccountScreen;
@@ -48,11 +51,17 @@ public class GroupListScreen extends AccountScreen {
@Override
protected void onLoad() {
super.onLoad();
addPanel.setVisible(false);
AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
@Override
public void onSuccess(AccountCapabilities ac) {
addPanel.setVisible(ac.canPerform(CREATE_GROUP));
}
}, CREATE_GROUP);
Util.GROUP_SVC
.visibleGroups(new ScreenLoadCallback<GroupList>(this) {
@Override
protected void preDisplay(GroupList result) {
addPanel.setVisible(result.isCanCreateGroup());
groups.display(result.getGroups());
groups.finishDisplay();
}

View File

@@ -14,8 +14,12 @@
package com.google.gerrit.client.admin;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountCapabilities;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.ProjectsTable;
@@ -33,10 +37,16 @@ public class ProjectListScreen extends Screen {
@Override
protected void onLoad() {
super.onLoad();
createProjectLinkPanel.setVisible(false);
AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
@Override
public void onSuccess(AccountCapabilities ac) {
createProjectLinkPanel.setVisible(ac.canPerform(CREATE_PROJECT));
}
}, CREATE_PROJECT);
Util.PROJECT_SVC.visibleProjects(new ScreenLoadCallback<ProjectList>(this) {
@Override
protected void preDisplay(final ProjectList result) {
createProjectLinkPanel.setVisible(result.canCreateProject());
projects.display(result.getProjects());
projects.finishDisplay();
}

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.httpd.raw.LegacyGerritServlet;
import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.httpd.raw.ToolServlet;
import com.google.gerrit.httpd.rpc.account.AccountCapabilitiesServlet;
import com.google.gerrit.httpd.rpc.project.ListProjectsServlet;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -72,6 +73,7 @@ class UrlModule extends ServletModule {
serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class);
filter("/a/*").through(RequireIdentifiedUserFilter.class);
serveRegex("^/(?:a/)?accounts/self/capabilities$").with(AccountCapabilitiesServlet.class);
serveRegex("^/(a/)?projects/$").with(ListProjectsServlet.class);
}

View File

@@ -0,0 +1,188 @@
// 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.httpd.rpc.account;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
import static com.google.gerrit.common.data.GlobalCapability.START_REPLICATION;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CONNECTIONS;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_QUEUE;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.httpd.RestApiServlet;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.git.QueueProvider;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.kohsuke.args4j.Option;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
public class AccountCapabilitiesServlet extends RestApiServlet {
private static final long serialVersionUID = 1L;
private final ParameterParser paramParser;
private final Provider<Impl> factory;
@Inject
AccountCapabilitiesServlet(
ParameterParser paramParser, Provider<Impl> factory) {
this.paramParser = paramParser;
this.factory = factory;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException {
Impl impl = factory.get();
if (acceptsJson(req)) {
impl.format = OutputFormat.JSON_COMPACT;
}
if (paramParser.parse(impl, req, res)) {
impl.compute();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
OutputStreamWriter out = new OutputStreamWriter(buf, "UTF-8");
if (impl.format.isJson()) {
res.setContentType(JSON_TYPE);
buf.write(JSON_MAGIC);
impl.format.newGson().toJson(
impl.have,
new TypeToken<Map<String, Object>>() {}.getType(),
out);
out.flush();
buf.write('\n');
} else {
res.setContentType("text/plain");
for (Map.Entry<String, Object> e : impl.have.entrySet()) {
out.write(e.getKey());
if (!(e.getValue() instanceof Boolean)) {
out.write(": ");
out.write(e.getValue().toString());
}
out.write('\n');
}
out.flush();
}
res.setCharacterEncoding("UTF-8");
send(req, res, buf.toByteArray());
}
}
static class Impl {
private final CapabilityControl cc;
private final Map<String, Object> have;
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
private OutputFormat format = OutputFormat.TEXT;
@Option(name = "-q", metaVar = "CAP", multiValued = true, usage = "Capability to inspect")
void addQuery(String name) {
if (query == null) {
query = Sets.newHashSet();
}
query.add(name.toLowerCase());
}
private Set<String> query;
@Inject
Impl(CurrentUser user) {
cc = user.getCapabilities();
have = Maps.newLinkedHashMap();
}
void compute() {
for (String name : GlobalCapability.getAllNames()) {
if (!name.equals(PRIORITY) && want(name) && cc.canPerform(name)) {
if (GlobalCapability.hasRange(name)) {
have.put(name, new Range(cc.getRange(name)));
} else {
have.put(name, true);
}
}
}
have.put(CREATE_ACCOUNT, cc.canCreateAccount());
have.put(CREATE_GROUP, cc.canCreateGroup());
have.put(CREATE_PROJECT, cc.canCreateProject());
have.put(KILL_TASK, cc.canKillTask());
have.put(VIEW_CACHES, cc.canViewCaches());
have.put(FLUSH_CACHES, cc.canFlushCaches());
have.put(VIEW_CONNECTIONS, cc.canViewConnections());
have.put(VIEW_QUEUE, cc.canViewQueue());
have.put(START_REPLICATION, cc.canStartReplication());
QueueProvider.QueueType queue = cc.getQueueType();
if (queue != QueueProvider.QueueType.INTERACTIVE
|| (query != null && query.contains(PRIORITY))) {
have.put(PRIORITY, queue);
}
Iterator<Map.Entry<String, Object>> itr = have.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, Object> e = itr.next();
if (!want(e.getKey())) {
itr.remove();
} else if (e.getValue() instanceof Boolean && !((Boolean) e.getValue())) {
itr.remove();
}
}
}
private boolean want(String name) {
return query == null || query.contains(name.toLowerCase());
}
}
private static class Range {
private transient PermissionRange range;
@SuppressWarnings("unused")
private int min;
@SuppressWarnings("unused")
private int max;
Range(PermissionRange r) {
range = r;
min = r.getMin();
max = r.getMax();
}
@Override
public String toString() {
return range.toString();
}
}
}