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:
@@ -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.
|
||||
|
@@ -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. */
|
||||
|
@@ -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; }-*/;
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user