Merge "Merge branch 'stable-3.3' into stable-3.4" into stable-3.4
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/BearerTokenProvider.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/BearerTokenProvider.java
new file mode 100644
index 0000000..be34319
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/BearerTokenProvider.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 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;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.Optional;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+public class BearerTokenProvider implements Provider<Optional<String>> {
+
+ private final Optional<String> bearerToken;
+
+ @Inject
+ public BearerTokenProvider(@GerritServerConfig Config gerritConfig) {
+ this.bearerToken = Optional.ofNullable(gerritConfig.getString("auth", null, "bearerToken"));
+ }
+
+ @Override
+ public Optional<String> get() {
+ return bearerToken;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
index 3e51e03..93bbde0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationModule.java
@@ -73,6 +73,7 @@
@Override
protected void configure() {
+ bind(BearerTokenProvider.class).in(Scopes.SINGLETON);
bind(RevisionReader.class).in(Scopes.SINGLETON);
bind(ApplyObject.class);
install(new FactoryModuleBuilder().build(FetchJob.Factory.class));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilter.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilter.java
new file mode 100644
index 0000000..cbe1ab8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilter.java
@@ -0,0 +1,133 @@
+// Copyright (C) 2022 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.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Authenticates the current user by HTTP bearer token authentication.
+ *
+ * <p>* @see <a href="https://www.rfc-editor.org/rfc/rfc6750">RFC 6750</a>
+ */
+public class BearerAuthenticationFilter extends AllRequestFilter {
+
+ private static final String BEARER_TOKEN = "BearerToken";
+ private final DynamicItem<WebSession> session;
+ private final String pluginName;
+ private final Provider<PluginUser> pluginUserProvider;
+ private final Provider<ThreadLocalRequestContext> threadLocalRequestContext;
+ private final String bearerToken;
+ private final Pattern bearerTokenRegex = Pattern.compile("^Bearer\\s(.+)$");
+
+ @Inject
+ BearerAuthenticationFilter(
+ DynamicItem<WebSession> session,
+ @PluginName String pluginName,
+ Provider<PluginUser> pluginUserProvider,
+ Provider<ThreadLocalRequestContext> threadLocalRequestContext,
+ @Named(BEARER_TOKEN) String bearerToken) {
+ this.session = session;
+ this.pluginName = pluginName;
+ this.pluginUserProvider = pluginUserProvider;
+ this.threadLocalRequestContext = threadLocalRequestContext;
+ this.bearerToken = bearerToken;
+ }
+
+ @Override
+ public void doFilter(
+ ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ if (!(servletRequest instanceof HttpServletRequest)
+ || !(servletResponse instanceof HttpServletResponse)) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ return;
+ }
+
+ HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
+ HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
+ String requestURI = httpRequest.getRequestURI();
+
+ if (isBasicAuthenticationRequest(requestURI)) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ } else if (isPullReplicationApiRequest(requestURI)) {
+ Optional<String> authorizationHeader =
+ Optional.ofNullable(httpRequest.getHeader("Authorization"));
+
+ if (isBearerTokenAuthenticated(authorizationHeader, bearerToken))
+ try (ManualRequestContext ctx =
+ new ManualRequestContext(pluginUserProvider.get(), threadLocalRequestContext.get())) {
+ WebSession ws = session.get();
+ ws.setAccessPathOk(AccessPath.REST_API, true);
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ else httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+ } else {
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ }
+
+ private boolean isBearerTokenAuthenticated(
+ Optional<String> authorizationHeader, String bearerToken) {
+ return authorizationHeader
+ .flatMap(this::extractBearerToken)
+ .map(bt -> bt.equals(bearerToken))
+ .orElse(false);
+ }
+
+ private boolean isBasicAuthenticationRequest(String requestURI) {
+ return requestURI.startsWith("/a/");
+ }
+
+ private boolean isPullReplicationApiRequest(String requestURI) {
+ return (requestURI.contains(pluginName)
+ && (requestURI.endsWith(String.format("/%s~apply-object", pluginName))
+ || requestURI.endsWith(String.format("/%s~apply-objects", pluginName))
+ || requestURI.endsWith(String.format("/%s~fetch", pluginName))
+ || requestURI.endsWith(String.format("/%s~delete-project", pluginName))
+ || requestURI.contains(String.format("/%s/init-project/", pluginName))))
+ || requestURI.matches(".*/projects/[^/]+/HEAD");
+ }
+
+ private Optional<String> extractBearerToken(String authorizationHeader) {
+ Matcher projectGroupMatcher = bearerTokenRegex.matcher(authorizationHeader);
+
+ if (projectGroupMatcher.find()) {
+ return Optional.of(projectGroupMatcher.group(1));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
index 83e8487..0f3e1e8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/HttpModule.java
@@ -19,14 +19,18 @@
import com.google.gerrit.server.config.GerritIsReplica;
import com.google.inject.Inject;
import com.google.inject.Scopes;
+import com.google.inject.name.Names;
import com.google.inject.servlet.ServletModule;
+import com.googlesource.gerrit.plugins.replication.pull.BearerTokenProvider;
public class HttpModule extends ServletModule {
private boolean isReplica;
+ private final BearerTokenProvider bearerTokenProvider;
@Inject
- public HttpModule(@GerritIsReplica Boolean isReplica) {
+ public HttpModule(@GerritIsReplica Boolean isReplica, BearerTokenProvider bearerTokenProvider) {
this.isReplica = isReplica;
+ this.bearerTokenProvider = bearerTokenProvider;
}
@Override
@@ -35,6 +39,16 @@
.to(PullReplicationApiMetricsFilter.class)
.in(Scopes.SINGLETON);
+ bearerTokenProvider
+ .get()
+ .ifPresent(
+ bt -> {
+ bind(String.class).annotatedWith(Names.named("BearerToken")).toInstance(bt);
+ DynamicSet.bind(binder(), AllRequestFilter.class)
+ .to(BearerAuthenticationFilter.class)
+ .in(Scopes.SINGLETON);
+ });
+
if (isReplica) {
DynamicSet.bind(binder(), AllRequestFilter.class)
.to(PullReplicationFilter.class)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index c1174c9..2214fb3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -108,9 +108,4 @@
Project.NameKey projectNameKey = Project.NameKey.parse(projectName);
return localFS.createProject(projectNameKey, RefNames.HEAD);
}
-
- public static String getProjectInitializationUrl(String pluginName, String projectName) {
- return String.format(
- "a/plugins/%s/init-project/%s", pluginName, Url.encode(projectName) + ".git");
- }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
index 09139f0..0afbecf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -15,7 +15,6 @@
package com.googlesource.gerrit.plugins.replication.pull.client;
import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
-import static com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction.getProjectInitializationUrl;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
@@ -33,6 +32,7 @@
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
+import com.googlesource.gerrit.plugins.replication.pull.BearerTokenProvider;
import com.googlesource.gerrit.plugins.replication.pull.Source;
import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiRequestMetrics;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
@@ -43,6 +43,7 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
+import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthenticationException;
@@ -73,6 +74,8 @@
private final String instanceId;
private final String pluginName;
private final SyncRefsFilter syncRefsFilter;
+ private final BearerTokenProvider bearerTokenProvider;
+ private final String urlAuthenticationPrefix;
@Inject
FetchRestApiClient(
@@ -82,6 +85,7 @@
SyncRefsFilter syncRefsFilter,
@PluginName String pluginName,
@Nullable @GerritInstanceId String instanceId,
+ BearerTokenProvider bearerTokenProvider,
@Assisted Source source) {
this.credentials = credentials;
this.httpClientFactory = httpClientFactory;
@@ -97,6 +101,9 @@
requireNonNull(
Strings.emptyToNull(this.instanceId),
"gerrit.instanceId or replication.instanceLabel must be set");
+
+ this.bearerTokenProvider = bearerTokenProvider;
+ this.urlAuthenticationPrefix = bearerTokenProvider.get().map(br -> "").orElse("a/");
}
/* (non-Javadoc)
@@ -105,11 +112,8 @@
@Override
public HttpResult callFetch(
Project.NameKey project, String refName, URIish targetUri, long startTimeNanos)
- throws ClientProtocolException, IOException {
- String url =
- String.format(
- "%s/a/projects/%s/pull-replication~fetch",
- targetUri.toString(), Url.encode(project.get()));
+ throws IOException {
+ String url = formatUrl(targetUri.toString(), project, "fetch");
Boolean callAsync = !syncRefsFilter.match(refName);
HttpPost post = new HttpPost(url);
post.setEntity(
@@ -122,7 +126,7 @@
post.addHeader(
PullReplicationApiRequestMetrics.HTTP_HEADER_X_START_TIME_NANOS,
Long.toString(startTimeNanos));
- return httpClientFactory.create(source).execute(withBasicAuthentication(targetUri, post), this);
+ return executeRequest(post, bearerTokenProvider.get(), targetUri);
}
/* (non-Javadoc)
@@ -130,13 +134,11 @@
*/
@Override
public HttpResult initProject(Project.NameKey project, URIish uri) throws IOException {
- String url =
- String.format(
- "%s/%s", uri.toString(), getProjectInitializationUrl(pluginName, project.get()));
+ String url = formatInitProjectUrl(uri.toString(), project);
HttpPut put = new HttpPut(url);
put.addHeader(new BasicHeader("Accept", MediaType.ANY_TEXT_TYPE.toString()));
put.addHeader(new BasicHeader("Content-Type", MediaType.PLAIN_TEXT_UTF_8.toString()));
- return httpClientFactory.create(source).execute(withBasicAuthentication(uri, put), this);
+ return executeRequest(put, bearerTokenProvider.get(), uri);
}
/* (non-Javadoc)
@@ -144,10 +146,9 @@
*/
@Override
public HttpResult deleteProject(Project.NameKey project, URIish apiUri) throws IOException {
- String url =
- String.format("%s/%s", apiUri.toASCIIString(), getProjectDeletionUrl(project.get()));
+ String url = formatUrl(apiUri.toASCIIString(), project, "delete-project");
HttpDelete delete = new HttpDelete(url);
- return httpClientFactory.create(source).execute(withBasicAuthentication(apiUri, delete), this);
+ return executeRequest(delete, bearerTokenProvider.get(), apiUri);
}
/* (non-Javadoc)
@@ -157,13 +158,12 @@
public HttpResult updateHead(Project.NameKey project, String newHead, URIish apiUri)
throws IOException {
logger.atFine().log("Updating head of %s on %s", project.get(), newHead);
- String url =
- String.format("%s/%s", apiUri.toASCIIString(), getProjectUpdateHeadUrl(project.get()));
+ String url = formatUrl(apiUri.toASCIIString(), project, "HEAD");
HttpPut req = new HttpPut(url);
req.setEntity(
new StringEntity(String.format("{\"ref\": \"%s\"}", newHead), StandardCharsets.UTF_8));
req.addHeader(new BasicHeader("Content-Type", MediaType.JSON_UTF_8.toString()));
- return httpClientFactory.create(source).execute(withBasicAuthentication(apiUri, req), this);
+ return executeRequest(req, bearerTokenProvider.get(), apiUri);
}
/* (non-Javadoc)
@@ -186,12 +186,12 @@
}
RevisionInput input = new RevisionInput(instanceId, refName, revisionData);
- String url = formatUrl(project, targetUri, "apply-object");
+ String url = formatUrl(targetUri.toString(), project, "apply-object");
HttpPost post = new HttpPost(url);
post.setEntity(new StringEntity(GSON.toJson(input)));
post.addHeader(new BasicHeader("Content-Type", MediaType.JSON_UTF_8.toString()));
- return httpClientFactory.create(source).execute(withBasicAuthentication(targetUri, post), this);
+ return executeRequest(post, bearerTokenProvider.get(), targetUri);
}
@Override
@@ -205,19 +205,23 @@
RevisionData[] inputData = new RevisionData[revisionData.size()];
RevisionsInput input = new RevisionsInput(instanceId, refName, revisionData.toArray(inputData));
- String url = formatUrl(project, targetUri, "apply-objects");
+ String url = formatUrl(targetUri.toString(), project, "apply-objects");
HttpPost post = new HttpPost(url);
post.setEntity(new StringEntity(GSON.toJson(input)));
post.addHeader(new BasicHeader("Content-Type", MediaType.JSON_UTF_8.toString()));
- return httpClientFactory.create(source).execute(withBasicAuthentication(targetUri, post), this);
+ return executeRequest(post, bearerTokenProvider.get(), targetUri);
}
- private String formatUrl(Project.NameKey project, URIish targetUri, String api) {
- String url =
- String.format(
- "%s/a/projects/%s/%s~%s",
- targetUri.toString(), Url.encode(project.get()), pluginName, api);
- return url;
+ private String formatUrl(String targetUri, Project.NameKey project, String api) {
+ return String.format(
+ "%s/%sprojects/%s/%s~%s",
+ targetUri, urlAuthenticationPrefix, Url.encode(project.get()), pluginName, api);
+ }
+
+ private String formatInitProjectUrl(String targetUri, Project.NameKey project) {
+ return String.format(
+ "%s/%splugins/%s/init-project/%s.git",
+ targetUri, urlAuthenticationPrefix, pluginName, Url.encode(project.get()));
}
private void requireNull(Object object, String string) {
@@ -245,6 +249,18 @@
return new HttpResult(response.getStatusLine().getStatusCode(), responseBody);
}
+ private HttpResult executeRequest(
+ HttpRequestBase httpRequest, Optional<String> bearerToken, URIish targetUri)
+ throws IOException {
+
+ HttpRequestBase reqWithAuthentication =
+ bearerToken.isPresent()
+ ? withBearerTokenAuthentication(httpRequest, bearerToken.get())
+ : withBasicAuthentication(targetUri, httpRequest);
+
+ return httpClientFactory.create(source).execute(reqWithAuthentication, this);
+ }
+
private HttpRequestBase withBasicAuthentication(URIish targetUri, HttpRequestBase req) {
org.eclipse.jgit.transport.CredentialsProvider cp =
credentials.create(source.getRemoteConfigName());
@@ -262,11 +278,8 @@
return req;
}
- String getProjectDeletionUrl(String projectName) {
- return String.format("a/projects/%s/%s~delete-project", Url.encode(projectName), pluginName);
- }
-
- String getProjectUpdateHeadUrl(String projectName) {
- return String.format("a/projects/%s/%s~HEAD", Url.encode(projectName), pluginName);
+ private HttpRequestBase withBearerTokenAuthentication(HttpRequestBase req, String bearerToken) {
+ req.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken));
+ return req;
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
index 55ad15c..20c1fea 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
@@ -42,6 +42,7 @@
import java.util.Base64;
import java.util.List;
import java.util.Optional;
+import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -82,7 +83,11 @@
SourceHttpClient.Factory httpClientFactory;
String url;
- protected abstract String getURL(String projectName);
+ protected abstract String getURLWithAuthenticationPrefix(String projectName);
+
+ protected String getURLWithoutAuthenticationPrefix(String projectName) {
+ return getURLWithAuthenticationPrefix(projectName).replace("a/", "");
+ }
@Override
public void setUpTestPlugin() throws Exception {
@@ -108,7 +113,7 @@
revisionReader = plugin.getSysInjector().getInstance(RevisionReader.class);
source = plugin.getSysInjector().getInstance(SourcesCollection.class).getAll().get(0);
- url = getURL(project.get());
+ url = getURLWithAuthenticationPrefix(project.get());
}
protected HttpPost createRequest(String sendObjectPayload) {
@@ -169,6 +174,12 @@
};
}
+ protected HttpRequestBase withBearerTokenAuthentication(
+ HttpRequestBase httpRequest, String bearerToken) {
+ httpRequest.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, "Bearer " + bearerToken));
+ return httpRequest;
+ }
+
protected HttpRequestBase withBasicAuthenticationAsAdmin(HttpRequestBase httpRequest)
throws AuthenticationException {
return withBasicAuthentication(httpRequest, admin);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionIT.java
index a5fd63c..2ab5caf 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionIT.java
@@ -180,6 +180,56 @@
assertHttpResponseCode(400));
}
+ @Test
+ @GerritConfig(name = "container.replica", value = "true")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldAcceptPayloadWhenNodeIsAReplicaWithBearerToken() throws Exception {
+ url = getURLWithoutAuthenticationPrefix(project.get());
+ String payloadWithoutAsyncFieldTemplate =
+ "{\"label\":\""
+ + TEST_REPLICATION_REMOTE
+ + "\",\"ref_name\":\"%s\",\"revision_data\":{\"commit_object\":{\"type\":1,\"content\":\"%s\"},\"tree_object\":{\"type\":2,\"content\":\"%s\"},\"blobs\":[]}}";
+
+ String refName = createRef();
+ Optional<RevisionData> revisionDataOption = createRevisionData(refName);
+ assertThat(revisionDataOption.isPresent()).isTrue();
+
+ RevisionData revisionData = revisionDataOption.get();
+ String sendObjectPayload =
+ createPayload(payloadWithoutAsyncFieldTemplate, refName, revisionData);
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createRequest(sendObjectPayload), "some-bearer-token"),
+ assertHttpResponseCode(201));
+ }
+
+ @Test
+ @GerritConfig(name = "container.replica", value = "false")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldAcceptPayloadWhenNodeIsAPrimaryWithBearerToken() throws Exception {
+ url = getURLWithoutAuthenticationPrefix(project.get());
+ String payloadWithoutAsyncFieldTemplate =
+ "{\"label\":\""
+ + TEST_REPLICATION_REMOTE
+ + "\",\"ref_name\":\"%s\",\"revision_data\":{\"commit_object\":{\"type\":1,\"content\":\"%s\"},\"tree_object\":{\"type\":2,\"content\":\"%s\"},\"blobs\":[]}}";
+
+ String refName = createRef();
+ Optional<RevisionData> revisionDataOption = createRevisionData(refName);
+ assertThat(revisionDataOption.isPresent()).isTrue();
+
+ RevisionData revisionData = revisionDataOption.get();
+ String sendObjectPayload =
+ createPayload(payloadWithoutAsyncFieldTemplate, refName, revisionData);
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createRequest(sendObjectPayload), "some-bearer-token"),
+ assertHttpResponseCode(201));
+ }
+
private String createPayload(
String wrongPayloadTemplate, String refName, RevisionData revisionData) {
String sendObjectPayload =
@@ -192,7 +242,7 @@
}
@Override
- protected String getURL(String projectName) {
+ protected String getURLWithAuthenticationPrefix(String projectName) {
return String.format(
"%s/a/projects/%s/pull-replication~apply-object",
adminRestSession.url(), Url.encode(projectName));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilterTest.java
new file mode 100644
index 0000000..bbbe66f
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/BearerAuthenticationFilterTest.java
@@ -0,0 +1,203 @@
+// Copyright (C) 2022 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 javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.inject.Provider;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BearerAuthenticationFilterTest {
+
+ @Mock private DynamicItem<WebSession> session;
+ @Mock private WebSession webSession;
+ @Mock private Provider<PluginUser> pluginUserProvider;
+ @Mock private Provider<ThreadLocalRequestContext> threadLocalRequestContextProvider;
+ @Mock private PluginUser pluginUser;
+ @Mock private ThreadLocalRequestContext threadLocalRequestContext;
+ @Mock private HttpServletRequest httpServletRequest;
+ @Mock private HttpServletResponse httpServletResponse;
+ @Mock private FilterChain filterChain;
+ private final String pluginName = "pull-replication";
+
+ private void authenticateWithURI(String uri) throws ServletException, IOException {
+ final String bearerToken = "some-bearer-token";
+ when(httpServletRequest.getRequestURI()).thenReturn(uri);
+ when(httpServletRequest.getHeader("Authorization"))
+ .thenReturn(String.format("Bearer %s", bearerToken));
+ when(pluginUserProvider.get()).thenReturn(pluginUser);
+ when(threadLocalRequestContextProvider.get()).thenReturn(threadLocalRequestContext);
+ when(session.get()).thenReturn(webSession);
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ bearerToken);
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(httpServletRequest).getHeader("Authorization");
+ verify(pluginUserProvider).get();
+ verify(threadLocalRequestContextProvider).get();
+ verify(session).get();
+ verify(webSession).setAccessPathOk(AccessPath.REST_API, true);
+ verify(filterChain).doFilter(httpServletRequest, httpServletResponse);
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenFetch() throws ServletException, IOException {
+ authenticateWithURI("any-prefix/pull-replication~fetch");
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenApplyObject()
+ throws ServletException, IOException {
+ authenticateWithURI("any-prefix/pull-replication~apply-object");
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenApplyObjects()
+ throws ServletException, IOException {
+ authenticateWithURI("any-prefix/pull-replication~apply-objects");
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenDeleteProject()
+ throws ServletException, IOException {
+ authenticateWithURI("any-prefix/pull-replication~delete-project");
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenUpdateHead()
+ throws ServletException, IOException {
+ authenticateWithURI("any-prefix/projects/my-project/HEAD");
+ }
+
+ @Test
+ public void shouldAuthenticateWithBearerTokenWhenInitProject()
+ throws ServletException, IOException {
+ authenticateWithURI("any-prefix/pull-replication/init-project/my-project.git");
+ }
+
+ @Test
+ public void shouldBe401WhenBearerTokenDoesNotMatch() throws ServletException, IOException {
+ when(httpServletRequest.getRequestURI()).thenReturn("any-prefix/pull-replication~fetch");
+ when(httpServletRequest.getHeader("Authorization"))
+ .thenReturn(String.format("Bearer %s", "some-different-bearer-token"));
+
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ "some-bearer-token");
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(httpServletRequest).getHeader("Authorization");
+ verify(httpServletResponse).sendError(SC_UNAUTHORIZED);
+ }
+
+ @Test
+ public void shouldBe401WhenBearerTokenCannotBeExtracted() throws ServletException, IOException {
+ when(httpServletRequest.getRequestURI()).thenReturn("any-prefix/pull-replication~fetch");
+ when(httpServletRequest.getHeader("Authorization")).thenReturn("bearer token");
+
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ "some-bearer-token");
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(httpServletRequest).getHeader("Authorization");
+ verify(httpServletResponse).sendError(SC_UNAUTHORIZED);
+ }
+
+ @Test
+ public void shouldBe401WhenNoAuthorizationHeaderInRequest() throws ServletException, IOException {
+ when(httpServletRequest.getRequestURI()).thenReturn("any-prefix/pull-replication~fetch");
+
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ "some-bearer-token");
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(httpServletResponse).sendError(SC_UNAUTHORIZED);
+ }
+
+ @Test
+ public void shouldGoNextInChainWhenUriDoesNotMatch() throws ServletException, IOException {
+ when(httpServletRequest.getRequestURI()).thenReturn("any-url");
+
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ "some-bearer-token");
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(filterChain).doFilter(httpServletRequest, httpServletResponse);
+ }
+
+ @Test
+ public void shouldGoNextInChainWhenBasicAuthorizationIsRequired()
+ throws ServletException, IOException {
+ when(httpServletRequest.getRequestURI())
+ .thenReturn("/a/projects/my-project/pull-replication~fetch");
+
+ final BearerAuthenticationFilter filter =
+ new BearerAuthenticationFilter(
+ session,
+ pluginName,
+ pluginUserProvider,
+ threadLocalRequestContextProvider,
+ "some-bearer-token");
+ filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
+
+ verify(httpServletRequest).getRequestURI();
+ verify(filterChain).doFilter(httpServletRequest, httpServletResponse);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionIT.java
index 5634b2a..cc01c2a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/FetchActionIT.java
@@ -78,8 +78,48 @@
.execute(createRequest(sendObjectPayload), assertHttpResponseCode(403));
}
+ @Test
+ @GerritConfig(name = "container.replica", value = "true")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldFetchRefWhenNodeIsAReplicaWithBearerToken() throws Exception {
+ String refName = createRef();
+ url = getURLWithoutAuthenticationPrefix(project.get());
+ String sendObjectPayload =
+ "{\"label\":\""
+ + TEST_REPLICATION_REMOTE
+ + "\", \"ref_name\": \""
+ + refName
+ + "\", \"async\":false}";
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createRequest(sendObjectPayload), "some-bearer-token"),
+ assertHttpResponseCode(201));
+ }
+
+ @Test
+ @GerritConfig(name = "container.replica", value = "false")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldFetchRefWhenNodeIsAPrimaryWithBearerToken() throws Exception {
+ String refName = createRef();
+ url = getURLWithoutAuthenticationPrefix(project.get());
+ String sendObjectPayload =
+ "{\"label\":\""
+ + TEST_REPLICATION_REMOTE
+ + "\", \"ref_name\": \""
+ + refName
+ + "\", \"async\":false}";
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createRequest(sendObjectPayload), "some-bearer-token"),
+ assertHttpResponseCode(201));
+ }
+
@Override
- protected String getURL(String projectName) {
+ protected String getURLWithAuthenticationPrefix(String projectName) {
return String.format(
"%s/a/projects/%s/pull-replication~fetch", adminRestSession.url(), Url.encode(projectName));
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
index 2f61cff..2953ed0 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
@@ -42,7 +42,7 @@
@Test
public void shouldDeleteRepositoryWhenUserHasProjectDeletionCapabilities() throws Exception {
String testProjectName = project.get();
- url = getURL(testProjectName);
+ url = getURLWithAuthenticationPrefix(testProjectName);
httpClientFactory
.create(source)
.execute(
@@ -65,7 +65,7 @@
@Test
public void shouldReturnOKWhenProjectIsDeleted() throws Exception {
String testProjectName = project.get();
- url = getURL(testProjectName);
+ url = getURLWithAuthenticationPrefix(testProjectName);
httpClientFactory
.create(source)
@@ -76,7 +76,7 @@
@Test
public void shouldReturnInternalServerErrorIfProjectCannotBeDeleted() throws Exception {
- url = getURL(INVALID_TEST_PROJECT_NAME);
+ url = getURLWithAuthenticationPrefix(INVALID_TEST_PROJECT_NAME);
httpClientFactory
.create(source)
@@ -97,7 +97,7 @@
@GerritConfig(name = "container.replica", value = "true")
public void shouldReturnOKWhenProjectIsDeletedOnReplica() throws Exception {
String testProjectName = project.get();
- url = getURL(testProjectName);
+ url = getURLWithAuthenticationPrefix(testProjectName);
httpClientFactory
.create(source)
@@ -111,7 +111,7 @@
public void shouldDeleteRepositoryWhenUserHasProjectDeletionCapabilitiesAndNodeIsAReplica()
throws Exception {
String testProjectName = project.get();
- url = getURL(testProjectName);
+ url = getURLWithAuthenticationPrefix(testProjectName);
HttpRequestBase deleteRequest = withBasicAuthenticationAsUser(createDeleteRequest());
httpClientFactory
@@ -133,7 +133,7 @@
@GerritConfig(name = "container.replica", value = "true")
public void shouldReturnInternalServerErrorIfProjectCannotBeDeletedWhenNodeIsAReplica()
throws Exception {
- url = getURL(INVALID_TEST_PROJECT_NAME);
+ url = getURLWithAuthenticationPrefix(INVALID_TEST_PROJECT_NAME);
httpClientFactory
.create(source)
@@ -142,8 +142,36 @@
assertHttpResponseCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
}
+ @Test
+ @GerritConfig(name = "container.replica", value = "true")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldReturnOKWhenProjectIsDeletedOnReplicaWithBearerToken() throws Exception {
+ String testProjectName = project.get();
+ url = getURLWithoutAuthenticationPrefix(testProjectName);
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createDeleteRequest(), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_OK));
+ }
+
+ @Test
+ @GerritConfig(name = "container.replica", value = "false")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldReturnOKWhenProjectIsDeletedOnPrimaryWithBearerToken() throws Exception {
+ String testProjectName = project.get();
+ url = getURLWithoutAuthenticationPrefix(testProjectName);
+
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createDeleteRequest(), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_OK));
+ }
+
@Override
- protected String getURL(String projectName) {
+ protected String getURLWithAuthenticationPrefix(String projectName) {
return String.format(
"%s/a/projects/%s/pull-replication~delete-project",
adminRestSession.url(), Url.encode(projectName));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
index d1242a0..99b3336 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationActionIT.java
@@ -15,7 +15,6 @@
package com.googlesource.gerrit.plugins.replication.pull.api;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
-import static com.googlesource.gerrit.plugins.replication.pull.api.ProjectInitializationAction.getProjectInitializationUrl;
import com.google.common.net.MediaType;
import com.google.gerrit.acceptance.config.GerritConfig;
@@ -56,7 +55,7 @@
@Test
public void shouldCreateRepository() throws Exception {
String newProjectName = "new/newProjectForPrimary";
- url = getURL(newProjectName);
+ url = getURLWithAuthenticationPrefix(newProjectName);
httpClientFactory
.create(source)
.execute(
@@ -75,7 +74,7 @@
@Test
public void shouldCreateRepositoryWhenUserHasProjectCreationCapabilities() throws Exception {
String newProjectName = "new/newProjectForUserWithCapabilities";
- url = getURL(newProjectName);
+ url = getURLWithAuthenticationPrefix(newProjectName);
HttpRequestBase put = withBasicAuthenticationAsUser(createPutRequestWithHeaders());
httpClientFactory
.create(source)
@@ -107,7 +106,7 @@
@GerritConfig(name = "container.replica", value = "true")
public void shouldCreateRepositoryWhenNodeIsAReplica() throws Exception {
String newProjectName = "new/newProjectForReplica";
- url = getURL(newProjectName);
+ url = getURLWithAuthenticationPrefix(newProjectName);
httpClientFactory
.create(source)
.execute(
@@ -130,7 +129,7 @@
public void shouldCreateRepositoryWhenUserHasProjectCreationCapabilitiesAndNodeIsAReplica()
throws Exception {
String newProjectName = "new/newProjectForUserWithCapabilitiesReplica";
- url = getURL(newProjectName);
+ url = getURLWithAuthenticationPrefix(newProjectName);
HttpRequestBase put = withBasicAuthenticationAsUser(createPutRequestWithHeaders());
httpClientFactory
.create(source)
@@ -153,7 +152,7 @@
@GerritConfig(name = "container.replica", value = "true")
public void shouldReturnInternalServerErrorIfProjectCannotBeCreatedWhenNodeIsAReplica()
throws Exception {
- url = getURL(INVALID_TEST_PROJECT_NAME);
+ url = getURLWithAuthenticationPrefix(INVALID_TEST_PROJECT_NAME);
httpClientFactory
.create(source)
.execute(
@@ -181,11 +180,36 @@
assertHttpResponseCode(HttpServletResponse.SC_FORBIDDEN));
}
+ @Test
+ @GerritConfig(name = "container.replica", value = "true")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldCreateRepositoryWhenNodeIsAReplicaWithBearerToken() throws Exception {
+ String newProjectName = "new/newProjectForReplica";
+ url = getURLWithoutAuthenticationPrefix(newProjectName);
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createPutRequestWithHeaders(), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_CREATED));
+ }
+
+ @Test
+ @GerritConfig(name = "container.replica", value = "false")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ public void shouldCreateRepositoryWhenNodeIsAPrimaryWithBearerToken() throws Exception {
+ String newProjectName = "new/newProjectForReplica";
+ url = getURLWithoutAuthenticationPrefix(newProjectName);
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(createPutRequestWithHeaders(), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_CREATED));
+ }
+
@Override
- protected String getURL(String projectName) {
+ protected String getURLWithAuthenticationPrefix(String projectName) {
return userRestSession.url()
- + "/"
- + getProjectInitializationUrl("pull-replication", Url.encode(projectName));
+ + String.format("/a/plugins/pull-replication/init-project/%s.git", Url.encode(projectName));
}
protected HttpPut createPutRequestWithHeaders() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/UpdateHeadActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/UpdateHeadActionIT.java
index 5355251..7c725b3 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/UpdateHeadActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/UpdateHeadActionIT.java
@@ -27,6 +27,7 @@
import com.google.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.methods.HttpRequestBase;
+import org.junit.Ignore;
import org.junit.Test;
public class UpdateHeadActionIT extends ActionITBase {
@@ -140,6 +141,50 @@
assertHttpResponseCode(HttpServletResponse.SC_FORBIDDEN));
}
+ @Test
+ @GerritConfig(name = "container.replica", value = "true")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ @Ignore("Waiting for resolving: Issue 16332: Not able to update the HEAD from internal user")
+ public void shouldReturnOKWhenHeadIsUpdatedInReplicaWithBearerToken() throws Exception {
+ String testProjectName = project.get();
+ url = getURLWithoutAuthenticationPrefix(testProjectName);
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(
+ createPutRequest(headInput(newBranch)), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_OK));
+
+ assertThat(gApi.projects().name(testProjectName).head()).isEqualTo(newBranch);
+ }
+
+ @Test
+ @GerritConfig(name = "container.replica", value = "false")
+ @GerritConfig(name = "auth.bearerToken", value = "some-bearer-token")
+ @Ignore("Waiting for resolving: Issue 16332: Not able to update the HEAD from internal user")
+ public void shouldReturnOKWhenHeadIsUpdatedInPrimaryWithBearerToken() throws Exception {
+ String testProjectName = project.get();
+ url = getURLWithoutAuthenticationPrefix(testProjectName);
+ String newBranch = "refs/heads/mybranch";
+ String master = "refs/heads/master";
+ BranchInput input = new BranchInput();
+ input.revision = master;
+ gApi.projects().name(testProjectName).branch(newBranch).create(input);
+ httpClientFactory
+ .create(source)
+ .execute(
+ withBearerTokenAuthentication(
+ createPutRequest(headInput(newBranch)), "some-bearer-token"),
+ assertHttpResponseCode(HttpServletResponse.SC_OK));
+
+ assertThat(gApi.projects().name(testProjectName).head()).isEqualTo(newBranch);
+ }
+
private String headInput(String ref) {
HeadInput headInput = new HeadInput();
headInput.ref = ref;
@@ -147,7 +192,7 @@
}
@Override
- protected String getURL(String projectName) {
+ protected String getURLWithAuthenticationPrefix(String projectName) {
return String.format("%s/a/projects/%s/HEAD", adminRestSession.url(), projectName);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
similarity index 85%
rename from src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
index a6886f1..a2389d7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientBase.java
@@ -16,9 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static javax.servlet.http.HttpServletResponse.SC_CREATED;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,6 +28,7 @@
import com.google.gerrit.entities.RefNames;
import com.googlesource.gerrit.plugins.replication.CredentialsFactory;
import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig;
+import com.googlesource.gerrit.plugins.replication.pull.BearerTokenProvider;
import com.googlesource.gerrit.plugins.replication.pull.Source;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData;
@@ -39,32 +38,25 @@
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.Collections;
-import java.util.Optional;
import org.apache.http.Header;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicHeader;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
-import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
-@RunWith(MockitoJUnitRunner.class)
-public class FetchRestApiClientTest {
+public abstract class FetchRestApiClientBase {
private static final boolean IS_REF_UPDATE = false;
@Mock CredentialsProvider credentialProvider;
@@ -74,10 +66,10 @@
@Mock FileBasedConfig config;
@Mock ReplicationFileBasedConfig replicationConfig;
@Mock Source source;
+ @Mock BearerTokenProvider bearerTokenProvider;
@Captor ArgumentCaptor<HttpPost> httpPostCaptor;
@Captor ArgumentCaptor<HttpPut> httpPutCaptor;
@Captor ArgumentCaptor<HttpDelete> httpDeleteCaptor;
-
String api = "http://gerrit-host";
String pluginName = "pull-replication";
String instanceId = "Replication";
@@ -150,43 +142,9 @@
FetchApiClient objectUnderTest;
- @Before
- public void setup() throws ClientProtocolException, IOException {
- when(credentialProvider.supports(any()))
- .thenAnswer(
- new Answer<Boolean>() {
+ protected abstract String urlAuthenticationPrefix();
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- CredentialItem.Username user = (CredentialItem.Username) invocation.getArgument(0);
- CredentialItem.Password password =
- (CredentialItem.Password) invocation.getArgument(1);
- user.setValue("admin");
- password.setValue("secret".toCharArray());
- return true;
- }
- });
-
- when(credentialProvider.get(any(), any(CredentialItem.class))).thenReturn(true);
- when(credentials.create(anyString())).thenReturn(credentialProvider);
- when(replicationConfig.getConfig()).thenReturn(config);
- when(config.getStringList("replication", null, "syncRefs")).thenReturn(new String[0]);
- when(source.getRemoteConfigName()).thenReturn("Replication");
-
- HttpResult httpResult = new HttpResult(SC_CREATED, Optional.of("result message"));
- when(httpClient.execute(any(HttpPost.class), any())).thenReturn(httpResult);
- when(httpClientFactory.create(any())).thenReturn(httpClient);
- syncRefsFilter = new SyncRefsFilter(replicationConfig);
- objectUnderTest =
- new FetchRestApiClient(
- credentials,
- httpClientFactory,
- replicationConfig,
- syncRefsFilter,
- pluginName,
- instanceId,
- source);
- }
+ protected abstract void assertAuthentication(HttpRequestBase httpRequest);
@Test
public void shouldCallFetchEndpoint()
@@ -199,24 +157,16 @@
HttpPost httpPost = httpPostCaptor.getValue();
assertThat(httpPost.getURI().getHost()).isEqualTo("gerrit-host");
assertThat(httpPost.getURI().getPath())
- .isEqualTo("/a/projects/test_repo/pull-replication~fetch");
+ .isEqualTo(
+ String.format(
+ "%s/projects/test_repo/pull-replication~fetch", urlAuthenticationPrefix()));
+ assertAuthentication(httpPost);
}
@Test
public void shouldByDefaultCallSyncFetchForAllRefs()
throws ClientProtocolException, IOException, URISyntaxException {
- syncRefsFilter = new SyncRefsFilter(replicationConfig);
- objectUnderTest =
- new FetchRestApiClient(
- credentials,
- httpClientFactory,
- replicationConfig,
- syncRefsFilter,
- pluginName,
- instanceId,
- source);
-
objectUnderTest.callFetch(Project.nameKey("test_repo"), refName, new URIish(api));
verify(httpClient, times(1)).execute(httpPostCaptor.capture(), any());
@@ -240,6 +190,7 @@
syncRefsFilter,
pluginName,
instanceId,
+ bearerTokenProvider,
source);
objectUnderTest.callFetch(Project.nameKey("test_repo"), refName, new URIish(api));
@@ -268,6 +219,7 @@
syncRefsFilter,
pluginName,
instanceId,
+ bearerTokenProvider,
source);
objectUnderTest.callFetch(Project.nameKey("test_repo"), refName, new URIish(api));
@@ -322,7 +274,10 @@
HttpPost httpPost = httpPostCaptor.getValue();
assertThat(httpPost.getURI().getHost()).isEqualTo("gerrit-host");
assertThat(httpPost.getURI().getPath())
- .isEqualTo("/a/projects/test_repo/pull-replication~apply-object");
+ .isEqualTo(
+ String.format(
+ "%s/projects/test_repo/pull-replication~apply-object", urlAuthenticationPrefix()));
+ assertAuthentication(httpPost);
}
@Test
@@ -367,6 +322,7 @@
syncRefsFilter,
pluginName,
null,
+ bearerTokenProvider,
source));
}
@@ -382,6 +338,7 @@
syncRefsFilter,
pluginName,
" ",
+ bearerTokenProvider,
source));
}
@@ -397,6 +354,7 @@
syncRefsFilter,
pluginName,
"",
+ bearerTokenProvider,
source));
}
@@ -412,6 +370,7 @@
syncRefsFilter,
pluginName,
"",
+ bearerTokenProvider,
source);
objectUnderTest.callFetch(Project.nameKey("test_repo"), refName, new URIish(api));
@@ -431,7 +390,11 @@
HttpPut httpPut = httpPutCaptor.getValue();
assertThat(httpPut.getURI().getHost()).isEqualTo("gerrit-host");
assertThat(httpPut.getURI().getPath())
- .isEqualTo("/a/plugins/pull-replication/init-project/test_repo.git");
+ .isEqualTo(
+ String.format(
+ "%s/plugins/pull-replication/init-project/test_repo.git",
+ urlAuthenticationPrefix()));
+ assertAuthentication(httpPut);
}
@Test
@@ -444,7 +407,11 @@
HttpDelete httpDelete = httpDeleteCaptor.getValue();
assertThat(httpDelete.getURI().getHost()).isEqualTo("gerrit-host");
assertThat(httpDelete.getURI().getPath())
- .isEqualTo("/a/projects/test_repo/pull-replication~delete-project");
+ .isEqualTo(
+ String.format(
+ "%s/projects/test_repo/pull-replication~delete-project",
+ urlAuthenticationPrefix()));
+ assertAuthentication(httpDelete);
}
@Test
@@ -462,8 +429,11 @@
assertThat(httpPut.getURI().getHost()).isEqualTo("gerrit-host");
assertThat(httpPut.getURI().getPath())
- .isEqualTo(String.format("/a/projects/%s/pull-replication~HEAD", projectName));
+ .isEqualTo(
+ String.format(
+ "%s/projects/%s/pull-replication~HEAD", urlAuthenticationPrefix(), projectName));
assertThat(payload).isEqualTo(String.format("{\"ref\": \"%s\"}", newHead));
+ assertAuthentication(httpPut);
}
public String readPayload(HttpPost entity) throws UnsupportedOperationException, IOException {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java
new file mode 100644
index 0000000..644afce
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2022 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.client;
+
+import static com.google.common.truth.Truth.assertThat;
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+import static org.mockito.Mockito.*;
+
+import com.googlesource.gerrit.plugins.replication.pull.filter.SyncRefsFilter;
+import java.io.IOException;
+import java.util.Optional;
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FetchRestApiClientWithBasicAuthenticationTest extends FetchRestApiClientBase {
+
+ @Before
+ public void setup() throws ClientProtocolException, IOException {
+ when(bearerTokenProvider.get()).thenReturn(Optional.empty());
+ when(credentialProvider.supports(any()))
+ .thenAnswer(
+ new Answer<Boolean>() {
+
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ CredentialItem.Username user = (CredentialItem.Username) invocation.getArgument(0);
+ CredentialItem.Password password =
+ (CredentialItem.Password) invocation.getArgument(1);
+ user.setValue("admin");
+ password.setValue("secret".toCharArray());
+ return true;
+ }
+ });
+
+ when(credentialProvider.get(any(), any(CredentialItem.class))).thenReturn(true);
+ when(credentials.create(anyString())).thenReturn(credentialProvider);
+ when(replicationConfig.getConfig()).thenReturn(config);
+ when(config.getStringList("replication", null, "syncRefs")).thenReturn(new String[0]);
+ when(source.getRemoteConfigName()).thenReturn("Replication");
+
+ HttpResult httpResult = new HttpResult(SC_CREATED, Optional.of("result message"));
+ when(httpClient.execute(any(HttpRequestBase.class), any())).thenReturn(httpResult);
+ when(httpClientFactory.create(any())).thenReturn(httpClient);
+ syncRefsFilter = new SyncRefsFilter(replicationConfig);
+ objectUnderTest =
+ new FetchRestApiClient(
+ credentials,
+ httpClientFactory,
+ replicationConfig,
+ syncRefsFilter,
+ pluginName,
+ instanceId,
+ bearerTokenProvider,
+ source);
+ verify(bearerTokenProvider).get();
+ }
+
+ @Override
+ protected String urlAuthenticationPrefix() {
+ return "/a";
+ }
+
+ @Override
+ protected void assertAuthentication(HttpRequestBase httpRequest) {
+ Header[] authorizationHeaders = httpRequest.getHeaders(HttpHeaders.AUTHORIZATION);
+ assertThat(authorizationHeaders.length).isEqualTo(1);
+ assertThat(authorizationHeaders[0].getValue()).contains("Basic");
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBearerTokenTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBearerTokenTest.java
new file mode 100644
index 0000000..90d71ad
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBearerTokenTest.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2022 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.client;
+
+import static com.google.common.truth.Truth.assertThat;
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+import static org.mockito.Mockito.*;
+
+import com.googlesource.gerrit.plugins.replication.pull.filter.SyncRefsFilter;
+import java.io.IOException;
+import java.util.Optional;
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FetchRestApiClientWithBearerTokenTest extends FetchRestApiClientBase {
+
+ @Before
+ public void setup() throws ClientProtocolException, IOException {
+ when(bearerTokenProvider.get()).thenReturn(Optional.of("some-bearer-token"));
+ when(replicationConfig.getConfig()).thenReturn(config);
+ when(config.getStringList("replication", null, "syncRefs")).thenReturn(new String[0]);
+ HttpResult httpResult = new HttpResult(SC_CREATED, Optional.of("result message"));
+ when(httpClient.execute(any(HttpRequestBase.class), any())).thenReturn(httpResult);
+ when(httpClientFactory.create(any())).thenReturn(httpClient);
+
+ syncRefsFilter = new SyncRefsFilter(replicationConfig);
+
+ objectUnderTest =
+ new FetchRestApiClient(
+ credentials,
+ httpClientFactory,
+ replicationConfig,
+ syncRefsFilter,
+ pluginName,
+ instanceId,
+ bearerTokenProvider,
+ source);
+ verify(bearerTokenProvider).get();
+ }
+
+ @Override
+ protected String urlAuthenticationPrefix() {
+ return "";
+ }
+
+ @Override
+ protected void assertAuthentication(HttpRequestBase httpRequest) {
+ Header[] authorizationHeaders = httpRequest.getHeaders(HttpHeaders.AUTHORIZATION);
+ assertThat(authorizationHeaders.length).isEqualTo(1);
+ assertThat(authorizationHeaders[0].getValue()).isEqualTo("Bearer " + "some-bearer-token");
+ }
+}