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)); } }