Add 'Pull Replication' capability
Add possibility to restrict call to Fetch REST endpoint
for triggering the pull replication.
Feature: Issue 12560
Change-Id: Id79069d6a7fd03fdcba1fdb68ebc6cb326394f4b
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
index d51590d..c44bcbc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchAction.java
@@ -20,6 +20,7 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -40,13 +41,18 @@
private final FetchCommand command;
private final WorkQueue workQueue;
private final DynamicItem<UrlFormatter> urlFormatter;
+ private final FetchPreconditions preConditions;
@Inject
public FetchAction(
- FetchCommand command, WorkQueue workQueue, DynamicItem<UrlFormatter> urlFormatter) {
+ FetchCommand command,
+ WorkQueue workQueue,
+ DynamicItem<UrlFormatter> urlFormatter,
+ FetchPreconditions preConditions) {
this.command = command;
this.workQueue = workQueue;
this.urlFormatter = urlFormatter;
+ this.preConditions = preConditions;
}
public static class Input {
@@ -57,6 +63,10 @@
@Override
public Response<?> apply(ProjectResource resource, Input input) throws RestApiException {
+
+ if (!preConditions.canCallFetchApi()) {
+ throw new AuthException("not allowed to call fetch command");
+ }
try {
if (Strings.isNullOrEmpty(input.label)) {
throw new BadRequestException("Source label cannot be null or empty");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java
new file mode 100644
index 0000000..73a4ac5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchApiCapability.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 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.googlesource.gerrit.plugins.replication.pull.api;
+
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+
+public class FetchApiCapability extends CapabilityDefinition {
+ static final String CALL_FETCH_ACTION = "callFetchAction";
+
+ @Override
+ public String getDescription() {
+ return "Pull Replication";
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java
new file mode 100644
index 0000000..ca1557a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchPreconditions.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2020 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.googlesource.gerrit.plugins.replication.pull.api;
+
+import static com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability.CALL_FETCH_ACTION;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.access.PluginPermission;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class FetchPreconditions {
+ private final String pluginName;
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ public FetchPreconditions(
+ @PluginName String pluginName,
+ Provider<CurrentUser> userProvider,
+ PermissionBackend permissionBackend) {
+ this.pluginName = pluginName;
+ this.userProvider = userProvider;
+ this.permissionBackend = permissionBackend;
+ }
+
+ public Boolean canCallFetchApi() {
+ PermissionBackend.WithUser userPermission = permissionBackend.user(userProvider.get());
+ return userPermission.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
+ || userPermission.testOrFalse(new PluginPermission(pluginName, CALL_FETCH_ACTION));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java
index 470c985..1663ad2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/PullReplicationApiModule.java
@@ -15,7 +15,10 @@
package com.googlesource.gerrit.plugins.replication.pull.api;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+import static com.googlesource.gerrit.plugins.replication.pull.api.FetchApiCapability.CALL_FETCH_ACTION;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.inject.Scopes;
@@ -24,5 +27,10 @@
protected void configure() {
bind(FetchAction.class).in(Scopes.SINGLETON);
post(PROJECT_KIND, "fetch").to(FetchAction.class);
+
+ bind(FetchPreconditions.class).in(Scopes.SINGLETON);
+ bind(CapabilityDefinition.class)
+ .annotatedWith(Exports.named(CALL_FETCH_ACTION))
+ .to(FetchApiCapability.class);
}
}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 46ec740..d921e0b 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -8,3 +8,10 @@
configuration is not recommended. It is also possible to specify a
local path as replication source. This makes e.g. sense if a network
share is mounted to which the repositories should be replicated from.
+
+Access
+------
+
+To be allowed to trigger pull replication a user must be a member of a
+group that is granted the 'Pull Replication' capability (provided
+by this plugin) or the 'Administrate Server' capability.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionTest.java
index 39c6717..2c888c8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionTest.java
@@ -18,11 +18,12 @@
import static org.apache.http.HttpStatus.SC_ACCEPTED;
import static org.apache.http.HttpStatus.SC_CREATED;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -60,6 +61,7 @@
@Mock DynamicItem<UrlFormatter> urlFormatterDynamicItem;
@Mock UrlFormatter urlFormatter;
@Mock WorkQueue.Task<Void> task;
+ @Mock FetchPreconditions preConditions;
@Before
public void setup() {
@@ -75,8 +77,9 @@
});
when(urlFormatterDynamicItem.get()).thenReturn(urlFormatter);
when(task.getTaskId()).thenReturn(taskId);
+ when(preConditions.canCallFetchApi()).thenReturn(true);
- fetchAction = new FetchAction(fetchCommand, workQueue, urlFormatterDynamicItem);
+ fetchAction = new FetchAction(fetchCommand, workQueue, urlFormatterDynamicItem, preConditions);
}
@Test
@@ -205,6 +208,18 @@
fetchAction.apply(projectResource, inputParams);
}
+ @Test(expected = AuthException.class)
+ public void shouldThrowAuthExceptionWhenCallFetchActionCapabilityNotAssigned()
+ throws RestApiException {
+ FetchAction.Input inputParams = new FetchAction.Input();
+ inputParams.label = label;
+ inputParams.refName = refName;
+
+ when(preConditions.canCallFetchApi()).thenReturn(false);
+
+ fetchAction.apply(projectResource, inputParams);
+ }
+
@Test
public void shouldReturnScheduledTaskForAsyncCall() throws RestApiException {
FetchAction.Input inputParams = new FetchAction.Input();