Fix permission handling

Users with MAINTAIN_SERVER capability were not able to get or change
the read only state. The reason was, that the @RequireAnyCapability
annotation does not support GlobalCapabilities provided by Gerrit core
in plugins.

This change fixes that by making an explicit permission check.

Change-Id: Ie38143587b9bd8dad2fabb2107e3195f6a3c1a90
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnly.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnly.java
new file mode 100644
index 0000000..89162f5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/DeleteReadOnly.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2024 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.readonly;
+
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+
+public class DeleteReadOnly extends ReadOnlyEndpoint
+    implements RestModifyView<ConfigResource, Input> {
+  private final ReadOnlyState state;
+
+  @Inject
+  public DeleteReadOnly(
+      Provider<CurrentUser> userProvider,
+      PermissionBackend permissionBackend,
+      ReadOnlyState state) {
+    super(userProvider, permissionBackend);
+    this.state = state;
+  }
+
+  @Override
+  public Response<String> apply(ConfigResource resource, Input input)
+      throws IOException, AuthException, PermissionBackendException {
+    checkPermissions();
+    state.setReadOnly(false);
+    return Response.ok("off");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableReadOnlyCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableReadOnlyCommand.java
index a64d0b1..75f5124 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableReadOnlyCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/DisableReadOnlyCommand.java
@@ -18,6 +18,8 @@
 import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
 
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
@@ -26,10 +28,10 @@
 @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
 @CommandMetaData(name = "disable", description = "Disable read only mode")
 class DisableReadOnlyCommand extends SshCommand {
-  @Inject ReadOnlyEndpoint.Delete delete;
+  @Inject DeleteReadOnly delete;
 
   @Override
-  protected void run() throws IOException {
+  protected void run() throws IOException, AuthException, PermissionBackendException {
     delete.apply(null, null);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/EnableReadOnlyCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/EnableReadOnlyCommand.java
index 322a9f9..c92717d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/EnableReadOnlyCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/EnableReadOnlyCommand.java
@@ -14,22 +14,19 @@
 
 package com.googlesource.gerrit.plugins.readonly;
 
-import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
-import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
-
-import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
 import java.io.IOException;
 
-@RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
 @CommandMetaData(name = "enable", description = "Enable read only mode")
 class EnableReadOnlyCommand extends SshCommand {
-  @Inject ReadOnlyEndpoint.Put put;
+  @Inject PutReadOnly put;
 
   @Override
-  protected void run() throws IOException {
+  protected void run() throws IOException, AuthException, PermissionBackendException {
     put.apply(null, null);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnly.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnly.java
new file mode 100644
index 0000000..0a3db04
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnly.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 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.readonly;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetReadOnly extends ReadOnlyEndpoint implements RestReadView<ConfigResource> {
+  private final ReadOnlyState state;
+
+  @Inject
+  public GetReadOnly(
+      Provider<CurrentUser> userProvider,
+      PermissionBackend permissionBackend,
+      ReadOnlyState state) {
+    super(userProvider, permissionBackend);
+    this.state = state;
+  }
+
+  @Override
+  public Response<String> apply(ConfigResource resource)
+      throws AuthException, PermissionBackendException {
+    checkPermissions();
+    return Response.ok(state.isReadOnly() ? "on" : "off");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyStatusCommand.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyStatusCommand.java
index 06a41e5..5ca5c94 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyStatusCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/GetReadOnlyStatusCommand.java
@@ -14,21 +14,18 @@
 
 package com.googlesource.gerrit.plugins.readonly;
 
-import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
-import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
-
-import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
 
-@RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
 @CommandMetaData(name = "status", description = "Show read only mode state")
 class GetReadOnlyStatusCommand extends SshCommand {
-  @Inject ReadOnlyEndpoint.Get get;
+  @Inject GetReadOnly get;
 
   @Override
-  protected void run() {
+  protected void run() throws AuthException, PermissionBackendException {
     stdout.println(get.apply(null));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
index 69fa804..6044f2e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/Module.java
@@ -29,9 +29,9 @@
         new RestApiModule() {
           @Override
           protected void configure() {
-            put(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Put.class);
-            delete(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Delete.class);
-            get(CONFIG_KIND, "readonly").to(ReadOnlyEndpoint.Get.class);
+            put(CONFIG_KIND, "readonly").to(PutReadOnly.class);
+            delete(CONFIG_KIND, "readonly").to(DeleteReadOnly.class);
+            get(CONFIG_KIND, "readonly").to(GetReadOnly.class);
           }
         });
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnly.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnly.java
new file mode 100644
index 0000000..92ca1be
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/PutReadOnly.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 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.readonly;
+
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+@Singleton
+public class PutReadOnly extends ReadOnlyEndpoint implements RestModifyView<ConfigResource, Input> {
+  private final ReadOnlyState state;
+
+  @Inject
+  public PutReadOnly(
+      Provider<CurrentUser> userProvider,
+      PermissionBackend permissionBackend,
+      ReadOnlyState state) {
+    super(userProvider, permissionBackend);
+    this.state = state;
+  }
+
+  @Override
+  public Response<String> apply(ConfigResource resource, Input input)
+      throws IOException, AuthException, PermissionBackendException {
+    checkPermissions();
+    state.setReadOnly(true);
+    return Response.ok("on");
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java
index 6a4b916..5b63a08 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnlyEndpoint.java
@@ -14,68 +14,31 @@
 
 package com.googlesource.gerrit.plugins.readonly;
 
-import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
-import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
+import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
+import static com.google.gerrit.server.permissions.GlobalPermission.MAINTAIN_SERVER;
 
-import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
-import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.config.ConfigResource;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Provider;
+import java.util.Set;
 
 public class ReadOnlyEndpoint {
-  static class Input {}
+  private final Provider<CurrentUser> userProvider;
+  private final PermissionBackend permissionBackend;
 
-  @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
-  @Singleton
-  public static class Get implements RestReadView<ConfigResource> {
-    private final ReadOnlyState state;
-
-    @Inject
-    Get(ReadOnlyState state) {
-      this.state = state;
-    }
-
-    @Override
-    public Response<String> apply(ConfigResource resource) {
-      return Response.ok(state.isReadOnly() ? "on" : "off");
-    }
+  public ReadOnlyEndpoint(Provider<CurrentUser> userProvider, PermissionBackend permissionBackend) {
+    this.userProvider = userProvider;
+    this.permissionBackend = permissionBackend;
   }
 
-  @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
-  @Singleton
-  public static class Put implements RestModifyView<ConfigResource, Input> {
-    private final ReadOnlyState state;
-
-    @Inject
-    Put(ReadOnlyState state) {
-      this.state = state;
+  void checkPermissions() throws AuthException, PermissionBackendException {
+    CurrentUser requestingUser = userProvider.get();
+    if (requestingUser == null || !requestingUser.isIdentifiedUser()) {
+      throw new AuthException("authentication required");
     }
 
-    @Override
-    public Response<String> apply(ConfigResource resource, Input input) throws IOException {
-      state.setReadOnly(true);
-      return Response.ok("on");
-    }
-  }
-
-  @RequiresAnyCapability({ADMINISTRATE_SERVER, MAINTAIN_SERVER})
-  @Singleton
-  public static class Delete implements RestModifyView<ConfigResource, Input> {
-    private final ReadOnlyState state;
-
-    @Inject
-    Delete(ReadOnlyState state) {
-      this.state = state;
-    }
-
-    @Override
-    public Response<String> apply(ConfigResource resource, Input input) throws IOException {
-      state.setReadOnly(false);
-      return Response.ok("off");
-    }
+    permissionBackend.user(requestingUser).checkAny(Set.of(ADMINISTRATE_SERVER, MAINTAIN_SERVER));
   }
 }