diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt index a3bfe2715b..bda424bb71 100644 --- a/Documentation/access-control.txt +++ b/Documentation/access-control.txt @@ -1266,6 +1266,15 @@ This limit applies not only to the link:cmd-query.html[`gerrit query`] command, but also to the web UI results pagination size. +[[capability_runAs]] +Run As +~~~~~~ + +Allow users to impersonate any other user with the X-Gerrit-RunAs +HTTP header on REST API calls. Site administrators do not inherit +this capability; it must be granted explicitly. + + [[capability_runGC]] Run Garbage Collection ~~~~~~~~~~~~~~~~~~~~~~ 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 b8de9aecf5..e18aee201a 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 @@ -70,6 +70,9 @@ public class GlobalCapability { /** Maximum result limit per executed query. */ public static final String QUERY_LIMIT = "queryLimit"; + /** Ability to impersonate another user. */ + public static final String RUN_AS = "runAs"; + /** Can run the Git garbage collection. */ public static final String RUN_GC = "runGC"; @@ -103,6 +106,7 @@ public class GlobalCapability { NAMES_ALL.add(KILL_TASK); NAMES_ALL.add(PRIORITY); NAMES_ALL.add(QUERY_LIMIT); + NAMES_ALL.add(RUN_AS); NAMES_ALL.add(RUN_GC); NAMES_ALL.add(START_REPLICATION); NAMES_ALL.add(STREAM_EVENTS); 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 ce277809d9..fe75b83e55 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 @@ -156,6 +156,7 @@ capabilityNames = \ killTask, \ priority, \ queryLimit, \ + runAs, \ runGC, \ startReplication, \ streamEvents, \ @@ -172,6 +173,7 @@ flushCaches = Flush Caches killTask = Kill Task priority = Priority queryLimit = Query Limit +runAs = Run As runGC = Run Garbage Collection startReplication = Start Replication streamEvents = Stream Events diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java index 96792f0886..80144c41cd 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java @@ -185,6 +185,7 @@ public final class CacheBasedWebSession implements WebSession { public void setUserAccountId(Account.Id id) { key = new Key("id:" + id); val = new Val(id, 0, false, null, 0, null, null); + user = null; } @Override diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java new file mode 100644 index 0000000000..8685fdf799 --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java @@ -0,0 +1,113 @@ +// Copyright (C) 2013 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; + +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + +import com.google.gerrit.httpd.restapi.RestApiServlet; +import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.account.AccountResolver; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.servlet.ServletModule; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** Allows running a request as another user account. */ +@Singleton +class RunAsFilter implements Filter { + private static final Logger log = LoggerFactory.getLogger(RunAsFilter.class); + private static final String RUN_AS = "X-Gerrit-RunAs"; + + static class Module extends ServletModule { + @Override + protected void configureServlets() { + filter("/*").through(RunAsFilter.class); + } + } + + private final Provider session; + private final AccountResolver accountResolver; + + @Inject + RunAsFilter(Provider session, AccountResolver accountResolver) { + this.session = session; + this.accountResolver = accountResolver; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse res, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + + String runas = req.getHeader(RUN_AS); + if (runas != null) { + CurrentUser self = session.get().getCurrentUser(); + if (!self.getCapabilities().canRunAs()) { + RestApiServlet.replyError( + (HttpServletResponse) res, + SC_FORBIDDEN, + "not permitted to use " + RUN_AS); + return; + } + + Account target; + try { + target = accountResolver.find(runas); + } catch (OrmException e) { + log.warn("cannot resolve account for " + RUN_AS, e); + RestApiServlet.replyError( + (HttpServletResponse) res, + SC_INTERNAL_SERVER_ERROR, + "cannot resolve " + RUN_AS); + return; + } + if (target == null) { + RestApiServlet.replyError( + (HttpServletResponse) res, + SC_FORBIDDEN, + "no account matches " + RUN_AS); + return; + } + session.get().setUserAccountId(target.getId()); + } + + chain.doFilter(req, res); + } + + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void destroy() { + } +} diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java index 7878477af2..efd8e242b5 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java @@ -83,6 +83,7 @@ public class WebModule extends FactoryModule { if (wantSSL) { install(new RequireSslFilter.Module()); } + install(new RunAsFilter.Module()); switch (authConfig.getAuthType()) { case HTTP: diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java index 842fcaf86a..fac950b4fc 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java @@ -841,8 +841,8 @@ public class RestApiServlet extends HttpServlet { } } - static void replyError(HttpServletResponse res, int statusCode, String msg) - throws IOException { + public static void replyError(HttpServletResponse res, int statusCode, + String msg) throws IOException { res.setStatus(statusCode); CacheHeaders.setNotCacheable(res); replyText(null, res, msg); 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 fad94655a3..4c5bd97b87 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 @@ -130,7 +130,6 @@ public class CapabilityControl { || canAdministrateServer(); } - /** @return true if the user can access the database (with gsql). */ public boolean canAccessDatabase() { return canPerform(GlobalCapability.ACCESS_DATABASE); @@ -160,6 +159,11 @@ public class CapabilityControl { || canAdministrateServer(); } + /** @return true if the user can impersonate another user. */ + public boolean canRunAs() { + return canPerform(GlobalCapability.RUN_AS); + } + /** @return which priority queue the user's tasks should be submitted to. */ public QueueProvider.QueueType getQueueType() { // If a non-generic group (that is not Anonymous Users or Registered Users)