Add 'Pull Replication' capability Add possibility to restrict call to Fetch REST endpoint for triggering the pull replication. Feature: Issue 12560 Change-Id: Id79069d6a7fd03fdcba1fdb68ebc6cb326394f4b (cherry picked from commit 0085e14bce7fba8ed73924dabf1e55243bc8c87f)
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 736fbf0..6970299 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();