Merge "Introduce pull-replication user and internal group" into stable-3.4
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 93bbde0..1671410 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
@@ -44,6 +44,7 @@
 import com.googlesource.gerrit.plugins.replication.StartReplicationCapability;
 import com.googlesource.gerrit.plugins.replication.pull.api.FetchJob;
 import com.googlesource.gerrit.plugins.replication.pull.api.PullReplicationApiModule;
+import com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupModule;
 import com.googlesource.gerrit.plugins.replication.pull.client.FetchApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient;
 import com.googlesource.gerrit.plugins.replication.pull.client.HttpClient;
@@ -73,6 +74,7 @@
   @Override
   protected void configure() {
 
+    install(new PullReplicationGroupModule());
     bind(BearerTokenProvider.class).in(Scopes.SINGLETON);
     bind(RevisionReader.class).in(Scopes.SINGLETON);
     bind(ApplyObject.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
index cbe1ab8..f196dbe 100644
--- 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
@@ -19,12 +19,12 @@
 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 com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationInternalUser;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.regex.Matcher;
@@ -46,7 +46,7 @@
   private static final String BEARER_TOKEN = "BearerToken";
   private final DynamicItem<WebSession> session;
   private final String pluginName;
-  private final Provider<PluginUser> pluginUserProvider;
+  private final PullReplicationInternalUser pluginUser;
   private final Provider<ThreadLocalRequestContext> threadLocalRequestContext;
   private final String bearerToken;
   private final Pattern bearerTokenRegex = Pattern.compile("^Bearer\\s(.+)$");
@@ -55,12 +55,12 @@
   BearerAuthenticationFilter(
       DynamicItem<WebSession> session,
       @PluginName String pluginName,
-      Provider<PluginUser> pluginUserProvider,
+      PullReplicationInternalUser pluginUser,
       Provider<ThreadLocalRequestContext> threadLocalRequestContext,
       @Named(BEARER_TOKEN) String bearerToken) {
     this.session = session;
     this.pluginName = pluginName;
-    this.pluginUserProvider = pluginUserProvider;
+    this.pluginUser = pluginUser;
     this.threadLocalRequestContext = threadLocalRequestContext;
     this.bearerToken = bearerToken;
   }
@@ -88,7 +88,7 @@
 
       if (isBearerTokenAuthenticated(authorizationHeader, bearerToken))
         try (ManualRequestContext ctx =
-            new ManualRequestContext(pluginUserProvider.get(), threadLocalRequestContext.get())) {
+            new ManualRequestContext(pluginUser, threadLocalRequestContext.get())) {
           WebSession ws = session.get();
           ws.setAccessPathOk(AccessPath.REST_API, true);
           filterChain.doFilter(servletRequest, servletResponse);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackend.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackend.java
new file mode 100644
index 0000000..9521004
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackend.java
@@ -0,0 +1,98 @@
+// 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.auth;
+
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.GroupDescription;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AbstractGroupBackend;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Arrays;
+import java.util.Collection;
+
+/** Backend to expose the pull-replication internal user group membership. */
+@Singleton
+class PullReplicationGroupBackend extends AbstractGroupBackend {
+  public static final AccountGroup.UUID INTERNAL_GROUP_UUID =
+      AccountGroup.uuid("pullreplication:internal-user");
+  public static final String INTERNAL_GROUP_NAME = "Pull-replication Internal User";
+  public static final String NAME_PREFIX = "pullreplication/";
+  public static final GroupDescription.Basic INTERNAL_GROUP_DESCRIPTION =
+      new GroupDescription.Basic() {
+
+        @Override
+        public String getUrl() {
+          return null;
+        }
+
+        @Override
+        public String getName() {
+          return INTERNAL_GROUP_NAME;
+        }
+
+        @Override
+        public AccountGroup.UUID getGroupUUID() {
+          return INTERNAL_GROUP_UUID;
+        }
+
+        @Override
+        public String getEmailAddress() {
+          return null;
+        }
+      };
+  private final PullReplicationInternalUser internalUser;
+
+  static final ListGroupMembership INTERNAL_GROUP_MEMBERSHIP =
+      new ListGroupMembership(
+          Arrays.asList(INTERNAL_GROUP_UUID, SystemGroupBackend.ANONYMOUS_USERS));
+
+  @Inject
+  public PullReplicationGroupBackend(PullReplicationInternalUser internalUser) {
+    this.internalUser = internalUser;
+  }
+
+  @Override
+  public boolean handles(AccountGroup.UUID uuid) {
+    return INTERNAL_GROUP_UUID.equals(uuid);
+  }
+
+  @Override
+  public GroupDescription.Basic get(AccountGroup.UUID uuid) {
+    return handles(uuid) ? INTERNAL_GROUP_DESCRIPTION : null;
+  }
+
+  @Override
+  public Collection<GroupReference> suggest(String name, ProjectState project) {
+    return Arrays.asList(
+        NAME_PREFIX.contains(name.toLowerCase())
+            ? GroupReference.create(INTERNAL_GROUP_UUID, INTERNAL_GROUP_NAME)
+            : GroupReference.create(name));
+  }
+
+  @Override
+  public GroupMembership membershipsOf(CurrentUser user) {
+    if (user.equals(internalUser)) {
+      return INTERNAL_GROUP_MEMBERSHIP;
+    }
+
+    return ListGroupMembership.EMPTY;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupModule.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupModule.java
new file mode 100644
index 0000000..583ba8e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupModule.java
@@ -0,0 +1,26 @@
+// 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.auth;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.inject.AbstractModule;
+
+public class PullReplicationGroupModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), GroupBackend.class).to(PullReplicationGroupBackend.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationInternalUser.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationInternalUser.java
new file mode 100644
index 0000000..e8b7666
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationInternalUser.java
@@ -0,0 +1,35 @@
+// 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.auth;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class PullReplicationInternalUser extends PluginUser {
+
+  @Inject
+  protected PullReplicationInternalUser(@PluginName String pluginName) {
+    super(pluginName);
+  }
+
+  @Override
+  public GroupMembership getEffectiveGroups() {
+    return PullReplicationGroupBackend.INTERNAL_GROUP_MEMBERSHIP;
+  }
+}
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
index bbbe66f..96c83a1 100644
--- 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
@@ -21,9 +21,9 @@
 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 com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationInternalUser;
 import java.io.IOException;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -39,9 +39,8 @@
 
   @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 PullReplicationInternalUser pluginUser;
   @Mock private ThreadLocalRequestContext threadLocalRequestContext;
   @Mock private HttpServletRequest httpServletRequest;
   @Mock private HttpServletResponse httpServletResponse;
@@ -53,21 +52,15 @@
     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);
+            session, pluginName, pluginUser, 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);
@@ -119,7 +112,7 @@
         new BearerAuthenticationFilter(
             session,
             pluginName,
-            pluginUserProvider,
+            pluginUser,
             threadLocalRequestContextProvider,
             "some-bearer-token");
     filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
@@ -138,7 +131,7 @@
         new BearerAuthenticationFilter(
             session,
             pluginName,
-            pluginUserProvider,
+            pluginUser,
             threadLocalRequestContextProvider,
             "some-bearer-token");
     filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
@@ -156,7 +149,7 @@
         new BearerAuthenticationFilter(
             session,
             pluginName,
-            pluginUserProvider,
+            pluginUser,
             threadLocalRequestContextProvider,
             "some-bearer-token");
     filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
@@ -173,7 +166,7 @@
         new BearerAuthenticationFilter(
             session,
             pluginName,
-            pluginUserProvider,
+            pluginUser,
             threadLocalRequestContextProvider,
             "some-bearer-token");
     filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
@@ -192,7 +185,7 @@
         new BearerAuthenticationFilter(
             session,
             pluginName,
-            pluginUserProvider,
+            pluginUser,
             threadLocalRequestContextProvider,
             "some-bearer-token");
     filter.doFilter(httpServletRequest, httpServletResponse, filterChain);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackendIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackendIT.java
new file mode 100644
index 0000000..87738e3
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/auth/PullReplicationGroupBackendIT.java
@@ -0,0 +1,80 @@
+// 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.auth;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupBackend.INTERNAL_GROUP_DESCRIPTION;
+import static com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupBackend.INTERNAL_GROUP_NAME;
+import static com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupBackend.INTERNAL_GROUP_UUID;
+import static com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupBackend.NAME_PREFIX;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.SkipProjectClone;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.entities.GroupDescription;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import java.util.Collection;
+import org.junit.Test;
+
+@SkipProjectClone
+@TestPlugin(
+    name = "pull-replication",
+    sysModule = "com.googlesource.gerrit.plugins.replication.pull.auth.PullReplicationGroupModule")
+public class PullReplicationGroupBackendIT extends LightweightPluginDaemonTest {
+
+  @Test
+  public void shouldResolvePullReplicationInternalGroup() {
+    GroupDescription.Basic group = groupBackend.get(INTERNAL_GROUP_UUID);
+
+    assertThat(group).isNotNull();
+    assertThat(group).isEqualTo(INTERNAL_GROUP_DESCRIPTION);
+  }
+
+  @Test
+  public void shouldSuggestPullReplicationInternalGroup() {
+    Collection<GroupReference> groups = groupBackend.suggest(NAME_PREFIX, null);
+
+    assertThat(groups).isNotNull();
+    assertThat(groups).hasSize(1);
+
+    GroupReference groupReference = groups.iterator().next();
+    assertThat(groupReference.getName()).isEqualTo(INTERNAL_GROUP_NAME);
+    assertThat(groupReference.getUUID()).isEqualTo(INTERNAL_GROUP_UUID);
+  }
+
+  @Test
+  public void pullReplicationInternalUserShouldHaveMembershipOfInternalGroupAndAnonymousUsers() {
+    assertMemberOfInternalAndAnonymousUsers(
+        groupBackend.membershipsOf(getPullReplicationInternalUser()));
+  }
+
+  @Test
+  public void pullReplicationInternalUserShouldHaveEffectiveGroups() {
+    assertMemberOfInternalAndAnonymousUsers(getPullReplicationInternalUser().getEffectiveGroups());
+  }
+
+  private CurrentUser getPullReplicationInternalUser() {
+    CurrentUser user = plugin.getSysInjector().getInstance(PullReplicationInternalUser.class);
+    return user;
+  }
+
+  private void assertMemberOfInternalAndAnonymousUsers(GroupMembership userMembership) {
+    assertThat(userMembership.contains(INTERNAL_GROUP_UUID)).isTrue();
+    assertThat(userMembership.contains(SystemGroupBackend.ANONYMOUS_USERS)).isTrue();
+  }
+}