This is an automated email from the ASF dual-hosted git repository.

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0d316940c8e2416130806b95e840bfd18eec9ab7
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Sep 2 15:29:32 2026 +0200

    [ENHANCEMENT] JMAP: allow disabling webpush
---
 docs/modules/servers/partials/configure/jmap.adoc  |   6 +
 .../sample-configuration/jmap.properties           |   4 +
 .../sample-configuration/jmap.properties           |   4 +
 .../sample-configuration/jmap.properties           |   4 +
 .../james/modules/event/JMAPEventBusModule.java    |   8 +-
 .../java/org/apache/james/jmap/JmapGuiceProbe.java |   6 +
 .../modules/protocols/JmapEventBusModule.java      |  27 +++-
 .../rfc8621/contract/WebPushDisabledContract.scala | 144 +++++++++++++++++++++
 ...ushTest.java => MemoryWebPushDisabledTest.java} |  21 +--
 .../jmap/rfc8621/memory/MemoryWebPushTest.java     |  11 ++
 .../james/jmap/core/JmapRfc8621Configuration.scala |   3 +
 .../jmap/method/PushSubscriptionGetMethod.scala    |  13 +-
 .../jmap/method/PushSubscriptionSetMethod.scala    |  13 +-
 13 files changed, 243 insertions(+), 21 deletions(-)

diff --git a/docs/modules/servers/partials/configure/jmap.adoc 
b/docs/modules/servers/partials/configure/jmap.adoc
index 5fe4cf5bd7..ac8f6708c7 100644
--- a/docs/modules/servers/partials/configure/jmap.adoc
+++ b/docs/modules/servers/partials/configure/jmap.adoc
@@ -104,6 +104,12 @@ then `apiUrl, downloadUrl, uploadUrl, eventSourceUrl, 
webSocketUrl` in response
 If the HTTP request to Jmap session endpoint has the `X-JMAP-WEBSOCKET-PREFIX` 
header with the value `ws://new-domain/prefix`,
 then `capabilities."urn:ietf:params:jmap:websocket".url` in response will be 
"ws://new-domain/prefix/jmap/ws".
 
+| webpush.enabled
+| Optional boolean. Defaults to true. Whether WebPush is enableded. When set 
to false the push listener is not
+registered at all - no group registration is created on the JMAP event bus - 
and `PushSubscription/get` and
+`PushSubscription/set` answer an `unknownMethod` JMAP error. Note that this 
does not affect JMAP push over
+WebSocket nor EventSource, which do not rely on push subscriptions.
+
 | webpush.prevent.server.side.request.forgery
 | Optional boolean. Prevent server side request forgery by preventing calls to 
the private network ranges. Defaults to true, can be disabled for testing.
 
diff --git a/server/apps/distributed-app/sample-configuration/jmap.properties 
b/server/apps/distributed-app/sample-configuration/jmap.properties
index 881fa372d6..b6d1ceec5b 100644
--- a/server/apps/distributed-app/sample-configuration/jmap.properties
+++ b/server/apps/distributed-app/sample-configuration/jmap.properties
@@ -41,5 +41,9 @@ tls.secret=james72laBalle
 # oidc.token.cache.maxSize=10000
 # oidc.token.cache.redis.command.timeout=3s
 
+# Whether WebPush is supported. Defaults to true. When set to false the push 
listener is not registered at all and
+# PushSubscription/get and PushSubscription/set answer an `unknownMethod` JMAP 
error.
+# webpush.enabled=false
+
 # Prevent server side request forgery by preventing calls to the private 
network ranges. Defaults to true, can be disabled for testing.
 # webpush.prevent.server.side.request.forgery=false
\ No newline at end of file
diff --git a/server/apps/memory-app/sample-configuration/jmap.properties 
b/server/apps/memory-app/sample-configuration/jmap.properties
index 40e4011bd9..7b255ed1bf 100644
--- a/server/apps/memory-app/sample-configuration/jmap.properties
+++ b/server/apps/memory-app/sample-configuration/jmap.properties
@@ -38,5 +38,9 @@ delay.sends.enabled=true
 # oidc.token.cache.expiration=5m
 # oidc.token.cache.maxSize=10000
 
+# Whether WebPush is supported. Defaults to true. When set to false the push 
listener is not registered at all and
+# PushSubscription/get and PushSubscription/set answer an `unknownMethod` JMAP 
error.
+# webpush.enabled=false
+
 # Prevent server side request forgery by preventing calls to the private 
network ranges. Defaults to true, can be disabled for testing.
 # webpush.prevent.server.side.request.forgery=false
\ No newline at end of file
diff --git a/server/apps/postgres-app/sample-configuration/jmap.properties 
b/server/apps/postgres-app/sample-configuration/jmap.properties
index 881fa372d6..b6d1ceec5b 100644
--- a/server/apps/postgres-app/sample-configuration/jmap.properties
+++ b/server/apps/postgres-app/sample-configuration/jmap.properties
@@ -41,5 +41,9 @@ tls.secret=james72laBalle
 # oidc.token.cache.maxSize=10000
 # oidc.token.cache.redis.command.timeout=3s
 
+# Whether WebPush is supported. Defaults to true. When set to false the push 
listener is not registered at all and
+# PushSubscription/get and PushSubscription/set answer an `unknownMethod` JMAP 
error.
+# webpush.enabled=false
+
 # Prevent server side request forgery by preventing calls to the private 
network ranges. Defaults to true, can be disabled for testing.
 # webpush.prevent.server.side.request.forgery=false
\ No newline at end of file
diff --git 
a/server/container/guice/distributed/src/main/java/org/apache/james/modules/event/JMAPEventBusModule.java
 
b/server/container/guice/distributed/src/main/java/org/apache/james/modules/event/JMAPEventBusModule.java
index ecb6bc46b9..d06f83b356 100644
--- 
a/server/container/guice/distributed/src/main/java/org/apache/james/modules/event/JMAPEventBusModule.java
+++ 
b/server/container/guice/distributed/src/main/java/org/apache/james/modules/event/JMAPEventBusModule.java
@@ -40,6 +40,7 @@ import org.apache.james.events.RoutingKeyConverter;
 import org.apache.james.jmap.InjectionKeys;
 import org.apache.james.jmap.change.Factory;
 import org.apache.james.jmap.change.JmapEventSerializer;
+import org.apache.james.jmap.core.JmapRfc8621Configuration;
 import org.apache.james.jmap.pushsubscription.PushListener;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -59,12 +60,15 @@ public class JMAPEventBusModule extends AbstractModule {
     }
 
     @ProvidesIntoSet
-    InitializationOperation workQueue(@Named(InjectionKeys.JMAP) 
RabbitMQEventBus instance, PushListener pushListener) {
+    InitializationOperation workQueue(@Named(InjectionKeys.JMAP) 
RabbitMQEventBus instance, PushListener pushListener,
+                                      JmapRfc8621Configuration 
jmapConfiguration) {
         return InitilizationOperationBuilder
             .forClass(RabbitMQEventBus.class)
             .init(() -> {
                 instance.start();
-                instance.register(pushListener);
+                if (jmapConfiguration.webPushEnabled()) {
+                    instance.register(pushListener);
+                }
             });
     }
 
diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JmapGuiceProbe.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JmapGuiceProbe.java
index 2ab5b5356e..9fdd420010 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JmapGuiceProbe.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JmapGuiceProbe.java
@@ -22,12 +22,14 @@ package org.apache.james.jmap;
 import static org.apache.james.jmap.utils.AccountIdUtil.toVacationAccountId;
 
 import java.util.Arrays;
+import java.util.Collection;
 
 import jakarta.inject.Inject;
 
 import org.apache.james.core.Username;
 import org.apache.james.events.EventBus;
 import org.apache.james.events.EventListener;
+import org.apache.james.events.Group;
 import org.apache.james.jmap.api.change.EmailChange;
 import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.MailboxChangeRepository;
@@ -75,6 +77,10 @@ public class JmapGuiceProbe implements GuiceProbe {
         return jmapServer.getPort();
     }
 
+    public Collection<Group> listRegisteredGroups() {
+        return eventBus.listRegisteredGroups();
+    }
+
     public void addEventListener(EventListener.GroupEventListener listener) {
         eventBus.register(listener);
     }
diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/modules/protocols/JmapEventBusModule.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/modules/protocols/JmapEventBusModule.java
index 71bdb0df15..cab33e090b 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/modules/protocols/JmapEventBusModule.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/modules/protocols/JmapEventBusModule.java
@@ -20,18 +20,39 @@
 package org.apache.james.modules.protocols;
 
 import org.apache.james.events.EventBus;
-import org.apache.james.events.EventListener;
 import org.apache.james.jmap.InjectionKeys;
+import org.apache.james.jmap.core.JmapRfc8621Configuration;
 import org.apache.james.jmap.pushsubscription.PushListener;
+import org.apache.james.modules.mailbox.ListenersConfiguration;
+import org.apache.james.modules.mailbox.MailboxListenersLoaderImpl;
+import org.apache.james.utils.InitializationOperation;
+import org.apache.james.utils.InitilizationOperationBuilder;
 
 import com.google.inject.AbstractModule;
-import com.google.inject.multibindings.Multibinder;
+import com.google.inject.multibindings.ProvidesIntoSet;
 import com.google.inject.name.Names;
 
 public class JmapEventBusModule extends AbstractModule {
     @Override
     protected void configure() {
         
bind(EventBus.class).annotatedWith(Names.named(InjectionKeys.JMAP)).to(EventBus.class);
-        Multibinder.newSetBinder(binder(), 
EventListener.ReactiveGroupEventListener.class).addBinding().to(PushListener.class);
+    }
+
+    /**
+     * Registered here rather than within the {@link 
org.apache.james.events.EventListener.ReactiveGroupEventListener}
+     * multibinder as WebPush can be turned off, in which case no registration 
- thus no group, and no queue for
+     * distributed event buses - should be created at all.
+     */
+    @ProvidesIntoSet
+    InitializationOperation registerPushListener(EventBus eventBus, 
PushListener pushListener,
+                                                 JmapRfc8621Configuration 
jmapConfiguration,
+                                                 ListenersConfiguration 
listenersConfiguration) {
+        return InitilizationOperationBuilder
+            .forClass(MailboxListenersLoaderImpl.class)
+            .init(() -> {
+                if (jmapConfiguration.webPushEnabled() && 
listenersConfiguration.isGroupListenerConsumptionEnabled()) {
+                    eventBus.register(pushListener);
+                }
+            });
     }
 }
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushDisabledContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushDisabledContract.scala
new file mode 100644
index 0000000000..84122210ad
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushDisabledContract.scala
@@ -0,0 +1,144 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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 org.apache.james.jmap.rfc8621.contract
+
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.JmapGuiceProbe
+import org.apache.james.jmap.change.MailboxChangeListenerGroup
+import org.apache.james.jmap.core.JmapRfc8621Configuration
+import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.pushsubscription.PushListenerGroup
+import org.apache.james.jmap.rfc8621.contract.Fixture._
+import org.apache.james.utils.DataProbeImpl
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+object WebPushDisabledContract {
+  val configuration: JmapRfc8621Configuration = JmapRfc8621Configuration(
+    urlPrefixString = "http://127.0.0.1";,
+    websocketPrefixString = "ws://127.0.0.1",
+    webPushEnabled = false)
+}
+
+trait WebPushDisabledContract {
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent()
+      .addDomain(DOMAIN.asString())
+      .addUser(BOB.asString(), BOB_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .build()
+  }
+
+  @Test
+  def pushListenerShouldNotBeRegistered(server: GuiceJamesServer): Unit = {
+    val groups = 
server.getProbe(classOf[JmapGuiceProbe]).listRegisteredGroups()
+
+    assertThat(groups).doesNotContain(PushListenerGroup())
+    // Ensures the above is not vacuously true: other JMAP listeners are 
registered on that very event bus
+    assertThat(groups).contains(MailboxChangeListenerGroup())
+  }
+
+  @Test
+  def pushSubscriptionGetShouldBeRejected(): Unit = {
+    val response = `given`
+      .body(
+        """{
+          |  "using": ["urn:ietf:params:jmap:core"],
+          |  "methodCalls": [[
+          |    "PushSubscription/get",
+          |    {
+          |      "ids": null
+          |    },
+          |    "c1"]]
+          |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "${SESSION_STATE.value}",
+         |  "methodResponses": [[
+         |    "error",
+         |    {
+         |      "type": "unknownMethod",
+         |      "description": "PushSubscription is disabled on this server: 
set `webpush.enabled` in jmap.properties to enable it"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+
+  @Test
+  def pushSubscriptionSetShouldBeRejected(): Unit = {
+    val response = `given`
+      .body(
+        """{
+          |  "using": ["urn:ietf:params:jmap:core"],
+          |  "methodCalls": [[
+          |    "PushSubscription/set",
+          |    {
+          |      "create": {
+          |        "4f29": {
+          |          "deviceClientId": "a889-ffea-910",
+          |          "url": 
"https://example.com/push/?device=X8980fc&client=12c6d086";,
+          |          "types": ["Mailbox"]
+          |        }
+          |      }
+          |    },
+          |    "c1"]]
+          |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response).isEqualTo(
+      s"""{
+         |  "sessionState": "${SESSION_STATE.value}",
+         |  "methodResponses": [[
+         |    "error",
+         |    {
+         |      "type": "unknownMethod",
+         |      "description": "PushSubscription is disabled on this server: 
set `webpush.enabled` in jmap.properties to enable it"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin)
+  }
+}
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushDisabledTest.java
similarity index 71%
copy from 
server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
copy to 
server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushDisabledTest.java
index ab6cbc0ba3..0eb3796fc5 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushDisabledTest.java
@@ -21,34 +21,27 @@ package org.apache.james.jmap.rfc8621.memory;
 
 import static 
org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 
-import org.apache.james.ClockExtension;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.MemoryJamesConfiguration;
 import org.apache.james.MemoryJamesServerMain;
-import org.apache.james.jmap.pushsubscription.PushClientConfiguration;
-import org.apache.james.jmap.rfc8621.contract.PushServerExtension;
-import org.apache.james.jmap.rfc8621.contract.PushSubscriptionProbeModule;
-import org.apache.james.jmap.rfc8621.contract.WebPushContract;
+import org.apache.james.jmap.core.JmapRfc8621Configuration;
+import org.apache.james.jmap.rfc8621.contract.WebPushDisabledContract;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-public class MemoryWebPushTest implements WebPushContract {
-
+public class MemoryWebPushDisabledTest implements WebPushDisabledContract {
     @RegisterExtension
     static JamesServerExtension testExtension = new 
JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
         MemoryJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
             .usersRepository(DEFAULT)
+            .enableJMAP()
             .build())
         .server(configuration -> 
MemoryJamesServerMain.createServer(configuration)
-            .overrideWith(new TestJMAPServerModule(), new 
PushSubscriptionProbeModule())
-            .overrideWith(binder -> 
binder.bind(PushClientConfiguration.class).toInstance(PushClientConfiguration.UNSAFE_DEFAULT())))
-        .extension(new ClockExtension())
-        .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+            .overrideWith(binder -> 
binder.bind(JmapRfc8621Configuration.class).toInstance(WebPushDisabledContract.configuration()))
+            .overrideWith(new TestJMAPServerModule()))
+        .lifeCycle(JamesServerExtension.Lifecycle.PER_TEST)
         .build();
-
-    @RegisterExtension
-    static PushServerExtension pushServerExtension = new PushServerExtension();
 }
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
index ab6cbc0ba3..2dd4a5d699 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryWebPushTest.java
@@ -20,17 +20,22 @@
 package org.apache.james.jmap.rfc8621.memory;
 
 import static 
org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.james.ClockExtension;
+import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.MemoryJamesConfiguration;
 import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.jmap.JmapGuiceProbe;
 import org.apache.james.jmap.pushsubscription.PushClientConfiguration;
+import org.apache.james.jmap.pushsubscription.PushListenerGroup;
 import org.apache.james.jmap.rfc8621.contract.PushServerExtension;
 import org.apache.james.jmap.rfc8621.contract.PushSubscriptionProbeModule;
 import org.apache.james.jmap.rfc8621.contract.WebPushContract;
 import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 public class MemoryWebPushTest implements WebPushContract {
@@ -51,4 +56,10 @@ public class MemoryWebPushTest implements WebPushContract {
 
     @RegisterExtension
     static PushServerExtension pushServerExtension = new PushServerExtension();
+
+    @Test
+    void pushListenerShouldBeRegisteredWhenWebPushIsEnabled(GuiceJamesServer 
server) {
+        
assertThat(server.getProbe(JmapGuiceProbe.class).listRegisteredGroups())
+            .contains(new PushListenerGroup());
+    }
 }
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
index c9f5098aa7..85d02bad67 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
@@ -42,6 +42,7 @@ object JmapConfigProperties {
   val URL_PREFIX_PROPERTY: String = "url.prefix"
   val WEBSOCKET_URL_PREFIX_PROPERTY: String = "websocket.url.prefix"
   val WEBSOCKET_PING_INTERVAL_PROPERTY: String = "websocket.ping.interval"
+  val WEB_PUSH_ENABLED_PROPERTY: String = "webpush.enabled"
   val WEB_PUSH_MAX_TIMEOUT_SECONDS_PROPERTY: String = 
"webpush.maxTimeoutSeconds"
   val WEB_PUSH_MAX_CONNECTIONS_PROPERTY: String = "webpush.maxConnections"
   val WEB_PUSH_PREVENT_SERVER_SIDE_REQUEST_FORGERY: String = 
"webpush.prevent.server.side.request.forgery"
@@ -104,6 +105,7 @@ object JmapRfc8621Configuration {
       maxObjectsInSet = 
Option(configuration.getLong(JMAP_SET_MAX_SIZE_PROPERTY, null))
         .map(value => MaxObjectsInSet(UnsignedInt.liftOrThrow(value)))
         .getOrElse(JMAP_MAX_OBJECT_IN_SET),
+      webPushEnabled = configuration.getBoolean(WEB_PUSH_ENABLED_PROPERTY, 
true),
       maxTimeoutSeconds = 
Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_TIMEOUT_SECONDS_PROPERTY,
 null)).map(Integer2int).toScala,
       maxConnections = 
Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_CONNECTIONS_PROPERTY, 
null)).map(Integer2int).toScala,
       preventServerSideRequestForgery = 
Optional.ofNullable(configuration.getBoolean(WEB_PUSH_PREVENT_SERVER_SIDE_REQUEST_FORGERY,
 null)).orElse(true),
@@ -127,6 +129,7 @@ case class JmapRfc8621Configuration(urlPrefixString: String,
                                     jmapEmailGetFullMaxSize: 
JmapEmailGetFullMaxSize = JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT,
                                     maxObjectsInGet: MaxObjectsInGet = 
JMAP_MAX_OBJECT_IN_GET,
                                     maxObjectsInSet: MaxObjectsInSet = 
JMAP_MAX_OBJECT_IN_SET,
+                                    webPushEnabled: Boolean = true,
                                     maxTimeoutSeconds: Option[Int] = None,
                                     maxConnections: Option[Int] = None,
                                     authenticationStrategies: 
Option[java.util.List[String]] = None,
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala
index 1f3bbf1021..e986cdcd89 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala
@@ -28,12 +28,13 @@ import 
org.apache.james.jmap.api.pushsubscription.PushSubscriptionHelpers.isInTh
 import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, 
JMAP_CORE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.core.{Ids, Invocation, JmapRfc8621Configuration, 
PushSubscriptionDTO, PushSubscriptionGetRequest, PushSubscriptionGetResponse, 
SessionTranslator, UnparsedPushSubscriptionId}
+import org.apache.james.jmap.core.{ErrorCode, Ids, Invocation, 
JmapRfc8621Configuration, PushSubscriptionDTO, PushSubscriptionGetRequest, 
PushSubscriptionGetResponse, SessionTranslator, UnparsedPushSubscriptionId}
 import org.apache.james.jmap.json.PushSubscriptionSerializer
 import org.apache.james.jmap.routes.SessionSupplier
 import org.apache.james.lifecycle.api.Startable
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.metrics.api.MetricFactory
+import org.reactivestreams.Publisher
 import play.api.libs.json.JsObject
 import reactor.core.scala.publisher.{SFlux, SMono}
 
@@ -58,6 +59,16 @@ class PushSubscriptionGetMethod 
@Inject()(pushSubscriptionSerializer: PushSubscr
   override val methodName: Invocation.MethodName = 
MethodName("PushSubscription/get")
   override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE)
 
+  override def process(capabilities: Set[CapabilityIdentifier], invocation: 
InvocationWithContext, mailboxSession: MailboxSession): 
Publisher[InvocationWithContext] =
+    if (configuration.webPushEnabled) {
+      super.process(capabilities, invocation, mailboxSession)
+    } else {
+      SMono.just(InvocationWithContext(Invocation.error(
+        errorCode = ErrorCode.UnknownMethod,
+        description = "PushSubscription is disabled on this server: set 
`webpush.enabled` in jmap.properties to enable it",
+        methodCallId = invocation.invocation.methodCallId), 
invocation.processingContext))
+    }
+
   override def getRequest(invocation: Invocation): Either[Exception, 
PushSubscriptionGetRequest] =
     
pushSubscriptionSerializer.deserializePushSubscriptionGetRequest(invocation.arguments.value).asEitherRequest
       .flatMap(request => request.validate(configuration).map(_ => request))
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala
index f6263f58b9..1e24277fac 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala
@@ -23,12 +23,13 @@ import eu.timepit.refined.auto._
 import jakarta.inject.Inject
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, 
JMAP_CORE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, 
PushSubscriptionSetRequest, PushSubscriptionSetResponse, SessionTranslator}
+import org.apache.james.jmap.core.{ErrorCode, Invocation, 
JmapRfc8621Configuration, PushSubscriptionSetRequest, 
PushSubscriptionSetResponse, SessionTranslator}
 import org.apache.james.jmap.json.PushSubscriptionSerializer
 import org.apache.james.jmap.routes.SessionSupplier
 import org.apache.james.lifecycle.api.Startable
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.metrics.api.MetricFactory
+import org.reactivestreams.Publisher
 import reactor.core.scala.publisher.SMono
 
 class PushSubscriptionSetMethod @Inject()(createPerformer: 
PushSubscriptionSetCreatePerformer,
@@ -42,6 +43,16 @@ class PushSubscriptionSetMethod @Inject()(createPerformer: 
PushSubscriptionSetCr
   override val methodName: Invocation.MethodName = 
MethodName("PushSubscription/set")
   override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE)
 
+  override def process(capabilities: Set[CapabilityIdentifier], invocation: 
InvocationWithContext, mailboxSession: MailboxSession): 
Publisher[InvocationWithContext] =
+    if (configuration.webPushEnabled) {
+      super.process(capabilities, invocation, mailboxSession)
+    } else {
+      SMono.just(InvocationWithContext(Invocation.error(
+        errorCode = ErrorCode.UnknownMethod,
+        description = "PushSubscription is disabled on this server: set 
`webpush.enabled` in jmap.properties to enable it",
+        methodCallId = invocation.invocation.methodCallId), 
invocation.processingContext))
+    }
+
   override def getRequest(invocation: Invocation): Either[Exception, 
PushSubscriptionSetRequest] =
     
pushSubscriptionSerializer.deserializePushSubscriptionSetRequest(invocation.arguments.value).asEitherRequest
       .flatMap(request => request.validate(configuration).map(_ => request))


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to