App Suite Releases
  • 8.47
  • 8.35
  • 7.10.6
Imprint
  • 8.47
  • 8.35
  • 7.10.6
Imprint
  • Release 8.54Upcoming
    • Noteworthy Changes
      • Important Changes
      • App Suite Middleware
    • Changelogs
      • App Suite UI
      • App Suite Middleware
      • Additional Components
        • AI Service
        • Booking Service
        • OX Guard UI
        • Switchboard
        • UI Service
    • Helm Charts
      • AI-Service documentation
      • App Suite Stack Chart
      • Booking
      • Helm Chart core-cacheservice
      • Helm Chart core-documentconverter
      • Helm Chart core-imageconverter
      • core-mw
      • UI Service
      • Switchboard
  • Release 8.53
  • Release 8.52
  • Release 8.51
  • Release 8.50
  • Release 8.49
Maintained. Older releases are best effort.
Upcoming
Not released yet
LTS
Long-term support branch

App Suite Middleware

8.54.350

Behavioral Changes

SCR-2048

Summary: User pseudonyms in exported feedback are keyed by a secret

Effective: 8.54.350 and later

Exported NPS and star rating feedback identifies users by a pseudonym. It was an MD5 hash of context and user ID, which anyone can trace back by hashing all IDs. It is now an HMAC-SHA256 keyed by com.openexchange.userfeedback.pseudonym.secret, so pseudonyms change once with the update and cannot be linked to earlier exports. Set the same secret on all nodes to keep pseudonyms stable across nodes and restarts.

SCR-2047

Summary: Invalid SSL trust level and missing trust stores no longer trust every certificate

Effective: 8.54.350 and later

An invalid value for com.openexchange.net.ssl.trustlevel (for example the typo restriced) now falls back to restricted instead of all, so certificates are validated; the error log names the fallback. If restricted is set but the JVM's default trust store is disabled via com.openexchange.net.ssl.default.truststore.enabled and no custom trust store is configured, Redis, S3, JMAP and Cassandra clients now validate against the JVM's default trust store instead of trusting every certificate. Valid values, including the default all, are not affected. If connections to self-signed endpoints fail after the update, check the configured trust level.

Configuration

SCR-2049

Summary: New configuration options for user feedback pseudonyms

Effective: 8.54.350 and later

A new option keys the pseudonyms of users in exported feedback.

  • com.openexchange.userfeedback.pseudonym.secret The secret for the HMAC-SHA256 pseudonyms of users in exported NPS and star rating feedback. Use the same value on all nodes. If empty, a random secret is used per node and start, so pseudonyms differ between nodes and restarts. Default empty. Reloadable, not config-cascade aware. No dedicated properties file.

8.54.346

3rd Party Libraries/License Change

SCR-2044

Summary: Removed the unused libraries eddsa, cose-java and nekohtml and replaced PBKDF2 with the JDK implementation

Effective: 8.54.346 and later

Removed three unused third-party libraries and replaced a fourth with the JDK implementation. No configuration change; no operator action is required.

  • Removed cose-java 1.1.0 and eddsa 0.3.0 from bundle com.openexchange.multifactor.provider.webauthn, together with the cbor 4.5.6, datautilities 1.1.0 and numbers 1.8.2 jars embedded alongside them. WebAuthn verification is unaffected; it runs in com.openexchange.webauthn, which still ships cbor.

  • Removed nekohtml 1.9.22 from bundle com.openexchange.common; the packages org.cyberneko.html* are no longer exported. Custom plugins that imported them from com.openexchange.common have to ship their own copy.

  • Replaced PBKDF2 1.1.4 and its transitive dependency picketbox 4.0.21.Final in bundle com.openexchange.crypto with the JDK's PBKDF2WithHmacSHA1; keys derived for legacy encrypted data are unchanged.

8.54.343

3rd Party Libraries/License Change

SCR-2041

Summary: Upgraded the Apache Kafka client from 4.0.0 to 4.0.2 to fix CVE-2026-35554 and CVE-2026-33558

Effective: 8.54.343 and later

Upgraded the Apache Kafka client library from 4.0.0 to 4.0.2 in the target platform. It is used by the Kafka logging appender of com.openexchange.logging. No configuration change; no operator action is required.

  • Fixes CVE-2026-35554 (high: concurrent producers could corrupt or misroute records) and CVE-2026-33558 (moderate: secrets in DEBUG logs).
  • Apache ServiceMix publishes no bundle beyond 4.0.0, so the platform now ships the upstream org.apache.kafka:kafka-clients jar with the former ServiceMix OSGi manifest; the bundle symbolic name org.apache.servicemix.bundles.kafka-clients stays the same, the jar is now org.apache.servicemix.bundles.kafka-clients-4.0.2-custom.jar.

8.54.338

Database

SCR-2035

Summary: Added index on the targeted shared account

Effective: 8.54.338 and later

Warning

Update Task{{com.openexchange.sharedaccount.storage.rdb.groupware.SharedAccountAddTargetIndexTask}}

Deleting a context or user removes the shared account permissions and user settings that target its shared accounts. Without a matching index this scanned and locked the whole table, so every insert into it waited until the deletion ended and could fail with a socket time-out. A new index sharedaccount_target on (sharedaccount_cid, sharedaccount_user) is added to the tables sharedaccount_permissions and sharedaccount_usersettings. Existing schemas get it through the update task; the ALTER TABLE runs online, but may take a while on large tables.

8.54.337

Database

SCR-2035

Summary: Added index on the targeted shared account

Effective: applies to the 8.54 release line

Warning

Update Task{{com.openexchange.sharedaccount.storage.rdb.groupware.SharedAccountAddTargetIndexTask}}

Deleting a context or user removes the shared account permissions and user settings that target its shared accounts. Without a matching index this scanned and locked the whole table, so every insert into it waited until the deletion ended and could fail with a socket time-out. A new index sharedaccount_target on (sharedaccount_cid, sharedaccount_user) is added to the tables sharedaccount_permissions and sharedaccount_usersettings. Existing schemas get it through the update task; the ALTER TABLE runs online, but may take a while on large tables.

8.54.336

Behavioral Changes

SCR-2030

Summary: Changed renewal of the tokens of external OAuth accounts

Effective: applies to the 8.54 release line

Access tokens of external OAuth accounts (Google, Box, Dropbox, Yahoo) are now renewed before they expire, and requests to the OAuth providers time out after 5 seconds for connecting and 10 seconds for reading. The cluster lock that serializes a renewal is now named after the account alone. During a rolling upgrade, nodes of the previous and the new version do not exclude each other, so an account whose tokens expire in that window may be renewed twice; for Box and Dropbox, which issue single-use refresh tokens, the user may then have to re-authorize the account. No admin action.

SCR-2029

Summary: Requests to the Google APIs honor the SSL configuration

Effective: applies to the 8.54 release line

Requests to the Google APIs for Drive, Calendar and Gmail used to accept any server certificate. They now use the SSL configuration of the middleware: with com.openexchange.net.ssl.trustlevel set to restricted, the certificate of Google is validated against the configured trust store. Deployments that reach Google through a TLS-intercepting proxy need its CA in that trust store. With the default all, nothing changes.

8.54.335

API - HTTP-API

SCR-2028

Summary: Changed status and scope handling of OAuth account requests

Effective: 8.54.335 and later

The status of an OAuth account association is now invalid_grant if the access token of the account is no longer valid, and ok if the OAuth provider or the database cannot be reached for the moment; recreation_needed remains for an account that cannot be used at all. oauth/accounts?action=init leaves out requested scopes that are disabled for the user through com.openexchange.oauth.modules.enabled.<provider> and refuses the request with OAUTH-0043 only if none remains. oauth/accounts?action=create requires the session secret like any other request; the token of the call-back URL no longer replaces it. oauth/proxy refuses provider responses larger than 4 MiB. The App Suite UI needs no adjustment.

SCR-1974

Summary: New capabilities access_tokens and mcp

Effective: 8.54.320 and later; also delivered in 8.54.335

Two new capabilities tell a client what to offer before it calls anything.

access_tokens is awarded to a user who may mint personal access tokens: the user is neither a guest nor anonymous, and the OAuth provider is enabled for the user. These are the checks the accesstoken module applies before minting, so a settings page shown only with this capability never offers what the module would refuse.

mcp is awarded to a user who can use the MCP endpoint: the endpoint is registered on the node, the user is neither a guest nor anonymous, and the OAuth provider is enabled for the user. It is independent of access_tokens, since personal access tokens serve more than the MCP endpoint and an MCP client may also bring a token from an external authorization server. The endpoint checks the capability itself on every request, including an override through com.openexchange.capability.forced.mcp, and refuses a disabled context there as well. Switching the OAuth provider or the capability off for a user or context therefore refuses that user's tokens with 403 at once, including tokens issued before.

No admin action. access_tokens follows com.openexchange.oauth.provider.enabled; mcp also follows com.openexchange.mcp.enabled and whether the open-xchange-mcp package is installed.

Behavioral Changes

SCR-1945

Summary: New MCP server endpoint for read-only access to mail, calendar, contacts, files and tasks

Effective: 8.54.320 and later; also delivered in 8.54.335

The middleware offers a Model Context Protocol server at /mcp (protocol revision 2026-07-28 and, over the same endpoint, the initialize handshake of revisions 2025-03-26, 2025-06-18 and 2025-11-25, which clients such as claude.ai connectors speak: no session identifier is issued, ping is answered, an unknown method is a JSON-RPC error on 200, and the mirroring headers are optional there, stateless HTTP POST) with read-only tools, prompts and resources. The tools are me_get, mail_search, mail_get, mail_attachment_get, calendar_list_events, calendar_get_event, calendar_free_busy, resources_search, contacts_search, contact_get, users_search, files_search, file_get, tasks_search, task_get, vacation_get, reminders_list and a folder tool per module (mail_folders, calendar_folders, contacts_folders, files_folders, tasks_folders); every list result can be paged through an opaque cursor argument and the nextCursor it returns. The prompts daily_briefing, inbox_triage, find_meeting_slot and catch_up_on chain those tools and are offered only where every tool they drive is available to the caller. Resources are read by URI through the templates ox:///files/{id}, ox:///mail/{folder}/{id} and ox:///mail/{folder}/{id}/attachment/{attachment}, as text, marked in _meta when it was cut, or, for anything without a text form, as its bytes up to 5 MiB, refusing a larger one with -32602 rather than cutting it; resources/list enumerates nothing. Callers authenticate with a bearer token that the OAuth provider validates, either a JWT from the configured authorization server or a personal access token; tools and resources are visible and usable only with the scopes read_mail, read_calendar, read_contacts, read_files, read_tasks and read_reminders, while me_get is open to every valid token. /.well-known/oauth-protected-resource/mcp serves the RFC 9728 resource metadata. Tool calls and resource reads are rate-limited per user, bounded in how many of one user run at once on a node, and written as one audit line each at level INFO through the logger com.openexchange.mcp.protocol.McpRequestHandler, without argument values or content, and measured through the meters appsuite.mcp.requests, appsuite.mcp.tool.calls and appsuite.mcp.authentications; the core-mw Grafana dashboard gained an MCP row. A bundle that still waits for a needed service, typically the OAuth provider, logs a warning naming it a minute after start. An OpenAPI document describing both paths, the transport headers, every method, the tools, prompts and resources is published as mcp.openapi.json under components/middleware/mcp/<version>/ next to the SCIM and provisioning documents. The endpoint is off by default and needs the chart feature mcp, com.openexchange.mcp.enabled=true and an enabled OAuth provider (com.openexchange.oauth.provider.enabled). The mail tools reach the primary mail account only; secondary and external accounts, which keep their own credentials, are outside a token session's reach. Documentation: documentation/administration/mcp_server.md.

Arguments that do not match a tool's input schema are answered as a tool result with isError on revisions 2025-11-25 and 2026-07-28, so that the model can correct the call; 2025-03-26 and 2025-06-18 keep the JSON-RPC error -32602. A tool call beyond the per-user rate limit or in-flight bound is a tool result with isError and Retry-After, a resource read beyond it a 429 carrying a JSON-RPC error. A valid token whose user may not use the endpoint (the capability mcp is checked on every request) or cannot sign in, such as a disabled user or context, is answered with 403 without a challenge; a token that cannot be checked right now, with 503 and Retry-After. A JSON-RPC batch is refused with -32600, and the earlier revisions answer a missing resource with -32002. Every tool carries annotations that mark it read-only. calendar_list_events covers every subscribed calendar the user sees across accounts and gives the user's participation status as myStatus; tasks_search gives all-day tasks as dates; vacation_get tells whether the notice replies today and its date range; reminders_list names the occurrence of a series; mail_search without a query lists the newest messages; users_search no longer fills in an e-mail address the address book withholds. A JWT can be tied to the endpoint through com.openexchange.mcp.audience (SCR-2027).

A request body has to hold exactly one JSON object; anything else is refused with 400 and -32700, a charset no runtime knows with 415. A nextCursor continues only the list it was handed out for: the same tool with the same arguments apart from cursor and limit. calendar_folders and contacts_folders list calendars and address books by the identifiers the calendar and contact tools report and take, such as cal://0/31 and con://0/32. calendar_list_events tells through calendar whether an event was read through the user's own, a shared or a public calendar. List results carry partial and warnings where a calendar, address book or folder could not be read, instead of failing or looking complete. mail_get joins every inline text part of a message and no longer lists inline images as attachments; a text file declared as application/octet-stream is served as text. For a caller without read access to the global address book, users_search matches names only and reports time zone and language for the caller alone. task_get, like the get action of the tasks module, refuses someone else's task in a folder where the caller may read only their own objects. Resource reads are measured through appsuite.mcp.resource.reads.

Configuration

SCR-2027

Summary: New configuration option for the audience of JWTs accepted by the MCP server

Effective: 8.54.335 and later

The MCP server endpoint /mcp can require that a JWT was issued for it.

  • com.openexchange.mcp.audience Comma-separated audiences of which a JWT has to name at least one in its aud claim to be accepted by /mcp; any other JWT is answered with 401 and invalid_token. Set it where the authorization server also issues tokens for other clients, such as the mobile app, since com.openexchange.oauth.provider.jwt.audience applies to every resource server of the installation alike. Empty accepts every JWT the OAuth provider accepts; the endpoint then logs a warning at start-up if com.openexchange.oauth.provider.jwt.jwksUri is set or the OAuth provider runs in mode token_introspection. Personal access tokens are not affected. Default empty. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1944

Summary: New configuration options for the MCP server

Effective: 8.54.335 and later

The MCP server endpoint /mcp is configured through these properties, read when the bundle com.openexchange.mcp starts and again on a configuration reload. A reload takes the endpoint down and up again only when com.openexchange.mcp.enabled changes; every other property takes effect while the endpoint keeps answering.

  • com.openexchange.mcp.enabled Whether the MCP endpoint /mcp and its resource metadata document /.well-known/oauth-protected-resource/mcp are registered. Default false. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.allowedOrigins Comma-separated Origin header values a browser-based client may send. A request with an Origin that is not listed is answered with 403. Default empty, which refuses every request carrying an Origin header. The endpoint sends no CORS headers and answers OPTIONS with 405, so a browser client on another origin needs CORS at the ingress. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.authorizationServers Comma-separated issuer URLs advertised as authorization_servers in the RFC 9728 resource metadata. Default empty; com.openexchange.oauth.provider.allowedIssuer is advertised then, if set. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.serverName The name reported as serverInfo.name. Default open-xchange-mcp. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.instructions The natural-language guidance a client receives from server/discover. Default: a sentence describing the read-only access to mail, calendar, contacts, files, tasks, reminders and the vacation notice. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.rateLimit.calls How many tool calls and resource reads a user may make per window, counted across all nodes through the rate limiter service. Beyond that a tool call is answered as a tool result with isError and a Retry-After header, a resource read with 429 and Retry-After. A value less than or equal to 0 (zero) disables the limit. Default 60. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.rateLimit.windowSeconds The window of the tool call limit in seconds. A value less than 1 is treated as 1. Default 60. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.maxConcurrentCalls How many tool calls and resource reads of one user may run at the same time on a node. A call holds its slot until its answer is written, so a client that reads slowly cannot pile up answers in memory. A further call is refused like one beyond the rate limit, with Retry-After: 1, takes no permit of the rate limit and is audited with outcome busy. Per node, since a call in flight lives on the node that runs it. A value less than or equal to 0 (zero) switches the bound off. Default 4. Reloadable, not config-cascade aware; a changed value applies to calls that start after the reload. No dedicated properties file.

8.54.333

API - HTTP-API

SCR-2026

Summary: New Mobile API endpoint for folder changes since a state

Effective: applies to the 8.54 release line

The Mobile API gains GET /mobile/v1/mail/folder-changes, with which native mail apps fetch the folders created, updated and destroyed since the state of an earlier folder list, instead of reloading the whole list. GET /mobile/v1/mail/folders now returns that state at the top level. The delta embeds the created and updated folders in folders and maps former to new identifiers in renamed for accounts whose folder identifiers change on a rename. A call returns at most limit identifiers (default and maximum 500) and sets hasMore when more follow. The state covers the accounts, subscribedOnly and, if the list asked for them, the counts of the list it came from. A state that is unknown, expired or whose account is gone is answered with 410 and code cannot_calculate_changes; the client reloads the list. The middleware keeps a snapshot per state in Redis, at most 50 and 1.5 MiB per user for 7 days after the last use; a list of more than about 5000 folders gets no state. IMAP and JMAP accounts are supported.

Behavioral Changes

SCR-2022

Summary: New metrics, dashboard panels and alerts for the Mobile API and push deliveries

Effective: applies to the 8.54 release line

The new counter appsuite_pns_deliveries_total counts the notifications handed to APNs and FCM by transport (apns_http2, fcm) and by the gateway's answer (result: accepted, invalid_token, invalid_request, rejected, failed). appsuite_mobile_requests_seconds gains histogram buckets from 50 ms to 30 s, so percentiles can be computed across pods. The Mobile API row of the core-mw Grafana dashboard adds the 95th percentile duration, the share of 503 answers, token sign-ins, push deliveries and open event streams. With extras.monitoring.alerts.enabled, the chart's PrometheusRule gains five alerts of severity warning: AppSuiteMobileApiUnavailable (more than 5% of a pod's requests answered with 503), AppSuiteMobileTokenValidationFailing (tokens cannot be validated), AppSuiteMobileApiSlow (95th percentile above one second), AppSuiteAccessTokenSignInsRateLimited (more than 20 rate-limited sign-ins in 15 minutes) and AppSuitePushDeliveriesFailing (more than 10% of a transport's pushes failed or refused). Shipped with chart version 6.25.23. No new configuration.

Configuration

SCR-2021

Summary: New configuration option for the header cache of Jetty connections

Effective: applies to the 8.54 release line

A new option sizes the header field cache each connection of the Jetty HTTP engine keeps. Its default is smaller than Jetty's own.

  • com.openexchange.http.jetty.headerCacheSize The size of the header field cache of each connection, in entries. The cache saves parsing repeated request headers on a kept-alive connection and lives as long as the connection. Jetty's own default of 1024 costs about 100 KB of heap per connection, which adds up for long-lived connections such as event streams. 0 switches the cache off. Default 256. Not reloadable, not config-cascade aware. File: jetty.properties.

8.54.332

API - HTTP-API

SCR-2017

Summary: New Mobile API endpoint to change many messages at once

Effective: 8.54.328 and later; also delivered in 8.54.331, 8.54.332

The Mobile API gains POST /mobile/v1/mail/message-bulk, with which native mail apps change any number of messages in one call: set seen, flagged, answered, color, categoryId and keywords, and move, copy, archive, spam, notSpam, trash or deletePermanently. Up to 50 operations run in order, each with its own result: 200, 207 with the failed identifiers and their problem, or an error status; a failing operation does not stop the others. Where message identifiers do not survive moves, the new ones are listed in moved; copies are listed in copies. The call needs the scope write_mail and requires Idempotency-Key; a retry after a lost response gets the kept result, which is kept up to 1 MB even if com.openexchange.ajax.idempotency.maxResultSize is lower. All operations together may name at most 1000 identifiers (Me.limits.maxBulkIds); more are answered with 413.

The response also carries states: for each folder the operations changed, the state right before and after them, as oldState and newState for GET /mobile/v1/mail/folders/{folderId}/message-changes. A folder is listed only if every change in between was one of the request's own; after a change by another client, or without CONDSTORE, it is left out and the app syncs as usual.

8.54.331

API - HTTP-API

SCR-2017

Summary: New Mobile API endpoint to change many messages at once

Effective: 8.54.328 and later; also delivered in 8.54.331

The Mobile API gains POST /mobile/v1/mail/message-bulk, with which native mail apps change any number of messages in one call: set seen, flagged, answered, color, categoryId and keywords, and move, copy, archive, spam, notSpam, trash or deletePermanently. Up to 50 operations run in order, each with its own result: 200, 207 with the failed identifiers and their problem, or an error status; a failing operation does not stop the others. Where message identifiers do not survive moves, the new ones are listed in moved; copies are listed in copies. The call needs the scope write_mail and requires Idempotency-Key; a retry after a lost response gets the kept result, which is kept up to 1 MB even if com.openexchange.ajax.idempotency.maxResultSize is lower. All operations together may name at most 1000 identifiers (Me.limits.maxBulkIds); more are answered with 413.

The response also carries states: for each folder the operations changed, the state right before and after them, as oldState and newState for GET /mobile/v1/mail/folders/{folderId}/message-changes. A folder is listed only if every change in between was one of the request's own; after a change by another client, or without CONDSTORE, it is left out and the app syncs as usual.

8.54.330

API - HTTP-API

SCR-2020

Summary: New Mobile API endpoints for message lists, conversations and search

Effective: 8.54.329 and later; also delivered in 8.54.330

The Mobile API gains GET /mobile/v1/mail/folders/{folderId}/messages, GET /mobile/v1/mail/folders/{folderId}/threads and POST /mobile/v1/mail/search. Native mail apps use them to list and search the messages and conversations of a folder, newest first and in pages. The cursor stays correct while mail arrives or is removed. Lists filter by unseen, flagged, categoryId and a receivedAfter window, and return the state for message-changes. A search field, operator or scope the mail server cannot search is answered with 400, never with all messages. hasAttachments follows the keywords $HasAttachment and $HasNoAttachment (Dovecot mail_attachment_detection_options = add-flags), else a multipart/mixed Content-Type, the same in lists and in search. Conversations need threading on the mail server (501 threading_unsupported otherwise). The endpoints need the scope read_mail.

SCR-2018

Summary: New Mobile API endpoints to read a message, its body, source, attachments and attachment previews

Effective: 8.54.329 and later; also delivered in 8.54.330

The Mobile API gains five operations for reading one message, all with scope read_mail and none of them marking the message seen: GET /mobile/v1/mail/messages/{messageId} returns headers, flags and, on request through fields, the attachment list; .../body returns the body sanitized against the server's allow-list, as HTML or text, with externalContent (block, proxy, allow) and maxBytes; .../raw and .../attachments/{attachmentId} return the source and an attachment with Range support (206, 416); .../attachments/{attachmentId}/preview returns a scaled image of a picture or, with the document converter, of a document's first page, 202 while it renders and 404 with code preview_unavailable without a converter. Where virus scanning is enforced, source, attachments and previews are scanned first.

8.54.329

API - HTTP-API

SCR-2020

Summary: New Mobile API endpoints for message lists, conversations and search

Effective: 8.54.329 and later

The Mobile API gains GET /mobile/v1/mail/folders/{folderId}/messages, GET /mobile/v1/mail/folders/{folderId}/threads and POST /mobile/v1/mail/search. Native mail apps use them to list and search the messages and conversations of a folder, newest first and in pages. The cursor stays correct while mail arrives or is removed. Lists filter by unseen, flagged, categoryId and a receivedAfter window, and return the state for message-changes. A search field, operator or scope the mail server cannot search is answered with 400, never with all messages. hasAttachments follows the keywords $HasAttachment and $HasNoAttachment (Dovecot mail_attachment_detection_options = add-flags), else a multipart/mixed Content-Type, the same in lists and in search. Conversations need threading on the mail server (501 threading_unsupported otherwise). The endpoints need the scope read_mail.

SCR-2018

Summary: New Mobile API endpoints to read a message, its body, source, attachments and attachment previews

Effective: 8.54.329 and later

The Mobile API gains five operations for reading one message, all with scope read_mail and none of them marking the message seen: GET /mobile/v1/mail/messages/{messageId} returns headers, flags and, on request through fields, the attachment list; .../body returns the body sanitized against the server's allow-list, as HTML or text, with externalContent (block, proxy, allow) and maxBytes; .../raw and .../attachments/{attachmentId} return the source and an attachment with Range support (206, 416); .../attachments/{attachmentId}/preview returns a scaled image of a picture or, with the document converter, of a document's first page, 202 while it renders and 404 with code preview_unavailable without a converter. Where virus scanning is enforced, source, attachments and previews are scanned first.

8.54.328

API - HTTP-API

SCR-2017

Summary: New Mobile API endpoint to change many messages at once

Effective: 8.54.328 and later

The Mobile API gains POST /mobile/v1/mail/message-bulk, with which native mail apps change any number of messages in one call: set seen, flagged, answered, color, categoryId and keywords, and move, copy, archive, spam, notSpam, trash or deletePermanently. Up to 50 operations run in order, each with its own result: 200, 207 with the failed identifiers and their problem, or an error status; a failing operation does not stop the others. Where message identifiers do not survive moves, the new ones are listed in moved; copies are listed in copies. The call needs the scope write_mail and requires Idempotency-Key; a retry after a lost response gets the kept result, which is kept up to 1 MB even if com.openexchange.ajax.idempotency.maxResultSize is lower. All operations together may name at most 1000 identifiers (Me.limits.maxBulkIds); more are answered with 413.

8.54.326

API - HTTP-API

SCR-2017

Summary: New Mobile API endpoint to change many messages at once

Effective: applies to the 8.54 release line

The Mobile API gains POST /mobile/v1/mail/message-bulk, with which native mail apps change any number of messages in one call: set seen, flagged, answered, color, categoryId and keywords, and move, copy, archive, spam, notSpam, trash or deletePermanently. Up to 50 operations run in order, each with its own result: 200, 207 with the failed identifiers and their problem, or an error status; a failing operation does not stop the others. Where message identifiers do not survive moves, the new ones are listed in moved; copies are listed in copies. The call needs the scope write_mail and requires Idempotency-Key; a retry after a lost response gets the kept result, which is kept up to 1 MB even if com.openexchange.ajax.idempotency.maxResultSize is lower. All operations together may name at most 1000 identifiers (Me.limits.maxBulkIds); more are answered with 413.

SCR-2010

Summary: New Mobile API endpoints for sign-in, account data and recipient suggestions

Effective: 8.54.320 and later

The Mobile API gains the endpoints a native mail app needs to sign in and load the account. In mode token, POST /mobile/v1/auth/token exchanges login, password and device name for a personal access token. A user with a second factor gets 401 with second_factor_required and a challenge, answered through POST /mobile/v1/auth/token/second-factor with a TOTP, backup or SMS code; a first call without code sends the SMS and returns 202. GET /mobile/v1/auth/token reads the presented token, DELETE /mobile/v1/auth/token revokes it. In mode idp all four answer 404. In both modes, GET /mobile/v1/me returns user, scopes and settings with an ETag; GET /mobile/v1/mail/accounts lists mail accounts with stableMessageIds and stableFolderIds; GET /mobile/v1/mail/signatures, GET /mobile/v1/mail/categories (with unread counts) and GET /mobile/v1/contacts/autocomplete return signatures, categories and recipient suggestions. Sign-in shares lockout and rate limits with the access token sign-in.

SCR-2007

Summary: New Mobile API endpoints for push subscriptions

Effective: 8.54.320 and later

The Mobile API gains /mobile/v1/push/subscriptions, with which native mail apps register a device for push notifications through APNs or FCM. POST registers the device, optionally with Idempotency-Key; GET lists the subscriptions of the token or OAuth client or reads one by subscriptionId; PATCH (application/merge-patch+json) changes one; DELETE removes it. All need the scope read_mail. Registering the same device token again replaces the subscription and keeps its identifier. Invalid input is answered with 400 or 422 and the field in errors[].pointer, a body over 64 KiB with 413. A subscription made with an access token ends with the token; one made in mode idp ends after 30 days without renewal.

SCR-1958

Summary: New Attribute sentFolderAccess for Deputy Permissions to Access the Granting User's Sent Folder

Effective: applies to the 8.54 release line

The DeputyPermission and GrantedDeputyPermission objects of the deputy module carry the new optional string attribute sentFolderAccess, granting the deputy access to the granting user's standard Sent folder of the primary mail account. It is independent of sendOnBehalfOf; with both granted, the copy of a mail sent on behalf is filed into the granting user's Sent folder as well, where it appears unread, and as before into the deputy's own.

  • none (default): no access. write: copies are filed, but the deputy cannot read the folder and it is not offered in the deputy's folder tree: it is reported with subscribed=false, and the granting user's INBOX as mounted in the deputy's tree reports subscr_subflds=false when that folder is its only subscribed child. readWrite: the deputy can read the folder and file into it, and it is subscribed.
  • PUT /appsuite/api/deputy?action=new and ?action=update accept the attribute; on update an absent attribute keeps the current value. GET ?action=all, ?action=get and ?action=reverse return it.
  • The new capability deputy_sent_folder tells a client whether the deployment offers the option (see SCR-1956). Newly granting or raising the access without it is rejected with DEPUTY-0017, while repeating the stored value or lowering it is accepted.
  • The access requires a mail module permission in the same deputy permission, otherwise DEPUTY-0015. Unknown values are rejected with SVL-0010, an unresolvable Sent folder with IMAP_DEPUTY-0016 or IMAP_DEPUTY-0017, and folder mode specific with the Sent folder but without the INBOX among the selected folders with IMAP_DEPUTY-0019, since the deputy's view of a covered Sent folder is derived from the INBOX (a selection without the Sent folder is not affected; an update that only changes sentFolderAccess is checked against the folders the permission currently covers).
  • If the copy cannot be filed, the send still succeeds and the response of the compose send request (PUT /appsuite/api/mail/compose/{id}/send) carries the new warning MSG-0134 in its warnings, so the deputy is not left believing the copy exists.

Purely additive - existing clients and payloads are unaffected. See the API documentation and the feature documentation for further details.

API - Java

SCR-1832

Summary: Relocated configuration and user-configuration Java packages

Effective: applies to the 8.54 release line

Custom bundles and plugins that use the configuration API must be adapted:

  • com.openexchange.config.ConfigurationService, Reloadable, Interests, PropertyFilter and related types moved to the com.openexchange.config.common package and bundle; import statements and bundle manifests must be repointed.
  • The UserConfiguration API moved to com.openexchange.config.universal; ServerSession.getUserConfiguration() now returns UserConfigurationImpl.

Code compiles against the old manifest imports but fails at OSGi resolution, so the manifest change is mandatory. The OX-maintained plugin repositories are adapted in the same release train.

API - RMI

SCR-1960

Summary: New Field sentFolderAccess in the Deputy Permission Data Objects of the Administrative RMI and gRPC Interfaces

Effective: applies to the 8.54 release line

The RMI data objects com.openexchange.admin.rmi.dataobjects.DeputyPermission (and thereby ActiveDeputyPermission) and DeputyPermissionDescription carry the new optional string field sentFolderAccess with the values none, write and readWrite, granting the deputy access to the granting user's standard Sent folder. The description object tracks it with the companion isSentFolderAccessSet flag, so an absent field keeps the current value on update.

The gRPC provisioning API mirrors it in deputy.proto: DeputyPermission.sent_folder_access (field 8) and the description pair sentFolderAccess (15) with SentFolderAccessSet (16). Semantics and validation match the SOAP interface, see SCR-1959. The rollback of a failed revoke-all on the shared account interface restores previously existing deputy permissions regardless of the deployment switch, so it cannot leave permissions half revoked.

Purely additive - existing RMI and gRPC clients are unaffected. See the feature documentation for further details.

API - SOAP

SCR-1959

Summary: New Element sentFolderAccess in the Deputy Permissions of the OXDeputyPermissionsService

Effective: applies to the 8.54 release line

The data objects DeputyPermission and ActiveDeputyPermission of the OXDeputyPermissionsService carry the new optional, nillable element sentFolderAccess with the values none (default), write and readWrite, granting the deputy access to the granting user's standard Sent folder (see SCR-1958 for the semantics). It is appended as the last element of both complex types, so the element sequence of the existing WSDL stays unchanged. The grant, grantMultiple and update operations accept it; get, list and listGivenTo return it. On update an absent element keeps the current value.

The access requires a mail module permission in the same deputy permission, otherwise the request fails with DEPUTY-0015; newly granting or raising it while the deployment has the option switched off fails with DEPUTY-0017 (see SCR-1956). Because this path has no session of the granting user, the standard Sent folder is resolved from the provisioning data and the mail settings; if it cannot be resolved the request fails with IMAP_DEPUTY-0016 or IMAP_DEPUTY-0017; a deputy from another context needs a DoveAdm connection with the namespace prefixes configured (IMAP_DEPUTY-0018 otherwise), and folder mode specific with the Sent folder but without the INBOX among the selected folders fails with IMAP_DEPUTY-0019. All of these fail before anything is stored. Existing SOAP clients are unaffected.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.admin.openexchange.com" xmlns:xsd="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:xsd1="http://dataobjects.rmi.admin.openexchange.com/xsd">
  <soapenv:Body>
    <soap:grant>
      <soap:deputyPermission>
        <xsd:userId>7</xsd:userId>
        <xsd:sendOnBehalfOf>true</xsd:sendOnBehalfOf>
        <xsd:modulePermissions>
          <xsd:moduleId>mail</xsd:moduleId>
          <xsd:folderPermission>2</xsd:folderPermission>
          <xsd:readPermission>4</xsd:readPermission>
          <xsd:writePermission>0</xsd:writePermission>
          <xsd:deletePermission>0</xsd:deletePermission>
          <xsd:admin>false</xsd:admin>
        </xsd:modulePermissions>
        <xsd:folderMode>default</xsd:folderMode>
        <xsd:sentFolderAccess>readWrite</xsd:sentFolderAccess>
      </soap:deputyPermission>
      <soap:context><xsd:id>1</xsd:id></soap:context>
      <soap:user><xsd:id>4</xsd:id></soap:user>
      <soap:auth>
        <xsd1:login>oxadmin</xsd1:login>
        <xsd1:password>secret</xsd1:password>
      </soap:auth>
    </soap:grant>
  </soapenv:Body>
</soapenv:Envelope>

See the feature documentation for further details.

Behavioral Changes

SCR-2016

Summary: Changed revoke of deputy permissions without a stored mail ACL baseline

Effective: applies to the 8.54 release line

Revoking a deputy permission restores the rights the deputy held on the granting user's mailboxes before the grant, taken from the mail ACL baseline in the table deputy_mail_acl_baseline (SCR-1853). Deputy permissions granted before that table existed have no row there. For those, the baseline is no longer read from its legacy location, the granting user's INBOX metadata entry /shared/vendor/vendor.open-xchange/deputydir-<deputyId>: every sharee of the INBOX, the deputy included, may write that entry, and a revoke would restore whatever rights it claims.

Such a permission now counts as having an empty baseline. Its revoke takes back all rights of the deputy on the granting user's mailboxes, including rights the deputy held before the grant and rights granted through another deputy permission for the same deputy. The next update of such a permission records the empty baseline in the table, so its eventual revoke behaves the same. Deputy permissions with a recorded baseline are not affected.

There is no switch for the previous behavior. No configuration change is required; after revoking an older deputy permission, operators or users may have to share affected folders with the deputy again.

SCR-2011

Summary: Signatures are sanitized when saved

Effective: 8.54.320 and later

HTML signatures created, updated or imported through the snippet module (new, update, import) are now sanitized. Before, the sanitized result was discarded and the given HTML stored. Saving a signature therefore removes markup outside the sanitizer's whitelist, such as sip: and callto: links or inline SVG. Plain-text signatures without HTML tags, such as Anna <anna@example.com>, stay unchanged; content that cannot be sanitized is stored as escaped text. Stored signatures change only when saved again. No admin action.

SCR-1990

Summary: Changed how Guard looks up recipient keys via DNS SRV records and remote key servers

Effective: applies to the 8.54 release line

Before asking the configured key servers, Guard looks for HKP servers announced in the _hkps._tcp and _hkp._tcp DNS SRV records of the recipient's domain. This lookup changes as follows:

  • All announced servers are tried in priority order (lower priority first, higher weight first within a priority) until one returns a key. Before, records with priority 0 were never queried, and only the first announced server was asked, even if it was unreachable. Guard may therefore now contact external key servers for domains whose SRV records it ignored so far.
  • The SRV part of a lookup, DNS queries included, uses at most half of com.openexchange.guard.remoteKeyLookupTimeout; the configured key servers always keep the rest. A whole lookup no longer exceeds that timeout. Before, a slow or unresponsive DNS server could use up the entire time, so the configured key servers were never asked and the recipient was treated as a guest.
  • The DNS lookup queries type SRV instead of ANY and no longer walks the resolver's search path.

The SRV lookup cannot be switched off. With com.openexchange.guard.dns.allowUnsignedSRVRecords=false, only DNSSEC-signed SRV records are used.

SCR-1969

Summary: MCP lists report 'returned' instead of 'totalMatches' and advertise only the scopes a node offers

Effective: applies to the 8.54 release line

Two corrections to what the MCP endpoint tells a client about itself.

A list result names the size of the page as returned instead of totalMatches. The field never held the number of matches there are: a tool asks its backend for one page plus a single entry, so the whole count is never known. A model reading totalMatches had every reason to believe it had seen everything. Whether more entries follow is what truncated and nextCursor say, and that is unchanged.

scopes_supported in the RFC 9728 resource metadata document, and the scope parameter of a WWW-Authenticate challenge, name the scopes the tools registered on that node require, rather than a fixed list. A deployment that leaves out a tool package no longer advertises that package's scope, so a client is not sent after access it gains nothing from.

Both are visible to a client. No admin action, no configuration, no migration.

SCR-1968

Summary: New error codes MCP-0001 to MCP-0003 for the MCP endpoint

Effective: applies to the 8.54 release line

The MCP endpoint and its tools raise error codes of their own instead of a general error. Until now every failure below the endpoint came back as a general OXException, so a log could only be filtered by matching the message text, and a node whose calendar stack was gone looked exactly like a caller sending nonsense.

MCP-0001 - The %1$s service is not available. A service a tool needs is not there: the node is missing the package behind that module, or it is still starting. Category SERVICE_DOWN.

MCP-0002 - Could not read the %1$s. A message, file or attachment could not be read; the cause is logged. Category ERROR.

MCP-0003 - Not a valid %1$s: %2$s. The caller sent a value the tool cannot use, so nothing is wrong with the node. Category USER_INPUT.

Callers see no new failure mode: the first two still reach a client as a tool error (200 with isError: true), the third as it did before. What changes is that an operator can alert on one kind of failure without matching free text. No admin action, no configuration.

SCR-1948

Summary: OAuth provider consults every token validator and answers 401 for a JWT of an unknown user

Effective: applies to the 8.54 release line

Three changes in com.openexchange.oauth.provider.impl: the resource-server side asks every registered OAuthAuthorizationService in ranking order and takes the first that recognizes a token, so that additional token kinds such as personal access tokens plug in next to the JWT validator; the provider starts in mode expect_jwt without com.openexchange.oauth.provider.jwt.jwksUri, then without a JWT validator, instead of failing to start; and a JWT whose subject resolves to no user is answered with 401 (invalid_token) instead of 500, logged at INFO.

A validator may hand parameters to the session the provider establishes (ValidationResponse.getSessionParameters()), and a validator that cannot check a token at all, e.g. because its storage is unavailable, reports that as TokenValidationUnavailableException; the HTTP API and the DAV interfaces then answer 503 (temporarily_unavailable, Retry-After) without a challenge, so that the client keeps its credential. No admin action.

Configuration

SCR-2003

Summary: New configuration options for Mobile API push notifications

Effective: 8.54.320 and later

The following options control push notifications for Mobile API apps through APNs and FCM. All are reloadable, config-cascade aware and have no dedicated properties file.

  • com.openexchange.mobile.push.notificationContent What a visible new-message push reveals to Apple or Google when a subscription asks for nothing: none (a neutral text), sender or senderAndSubject. Default none. An unknown value counts as none.

  • com.openexchange.mobile.push.maxNotificationContent The most a subscription may ask a new-message push to reveal; a higher request is lowered to it, also for existing subscriptions. Default senderAndSubject. An unknown value counts as none.

  • com.openexchange.mobile.push.maxSubscriptionsPerUser The most push subscriptions a user may hold across all Mobile API apps; a further registration is refused with 422. Registering a device again does not count. 0 means no limit. Default 20.

SCR-2002

Summary: New Mobile API endpoints for sending, drafts and uploads, and configuration options for uploads

Effective: applies to the 8.54 release line

The Mobile API gains endpoints to send mails, keep drafts and upload attachments:

  • POST /mobile/v1/mail/send sends a mail without a draft. It requires an Idempotency-Key; a retry with the same key or clientMessageId is answered with duplicate instead of sending the mail again.
  • GET and POST /mobile/v1/mail/drafts, GET, PUT and DELETE /mobile/v1/mail/drafts/{draftId} and POST /mobile/v1/mail/drafts/{draftId}/send manage drafts. PUT requires If-Match: without it the answer is 428, and if the draft changed meanwhile it is 412 with the current draft.
  • POST /mobile/v1/uploads stores a file to attach, DELETE /mobile/v1/uploads/{uploadId} removes it. An upload needs Upload-Length and cannot be part of a batch.

Reading drafts needs the scope read_mail, all other endpoints write_mail. Request bodies of sends and drafts are limited to 4 MiB (413).

The following options limit the files an app stores through the Mobile API at /mobile/v1 to attach them to mails. Uploads are kept for 24 hours and do not count against the user's quota. Both limits hold exactly only with Redis Lua scripting (com.openexchange.redis.lua.enabled, the default); without it, simultaneous uploads may exceed them slightly.

  • com.openexchange.mobile.api.uploads.maxCount How many uploads a user may keep at a time; a further upload is refused with 507 and code quota_exceeded. At most 10000; a value less than or equal to 0 (zero), or a higher one, means 10000. Default 100. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.mobile.api.uploads.maxSize How many bytes the uploads of a user may take up in total; beyond that an upload is refused with 507 and code quota_exceeded. A single upload is limited like a mail attachment (MAX_UPLOAD_SIZE and the user's upload quotas). A value less than or equal to 0 (zero) sets no limit. Default 1073741824 (1 GiB). Reloadable, config-cascade aware. No dedicated properties file.

SCR-1962

Summary: New configuration options for personal access tokens

Effective: applies to the 8.54 release line

Two limits for personal access tokens, the bearer credentials a user mints through the HTTP API module accesstoken.

  • com.openexchange.accesstoken.maxLifetimeDays The longest lifetime a user may give a token, in days from the moment it is minted; a request with a later expires is refused with ACCESSTOKEN-0007. A value of 0 (zero) removes the limit. Default 365. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.maxTokensPerUser How many tokens a user may hold at once; expired tokens count until the clean-up drops them a week after their expiry. Minting beyond the limit is refused with ACCESSTOKEN-0006. A value of 0 (zero) removes the limit. Default 50. Reloadable, config-cascade aware. No dedicated properties file.

SCR-1956

Summary: New Properties and Capability for Deputy Sent Folder Access

Effective: applies to the 8.54 release line

In order to let a granting user receive a copy of every mail a deputy sends in their name, the Sent folder access option is introduced, controlled by two new lean configuration properties and announced through a new capability.

  • com.openexchange.deputy.sentFolderAccessEnabled makes the option available. It defaults to false, so operators enable the option deliberately; it is reloadable and config-cascade aware, and is evaluated in the granting user's scope. While it is false, newly granting or raising the access is rejected with DEPUTY-0017 and no copy is filed any more - including for permissions that already carry it, so the copies can be withdrawn deployment-wide without revoking permissions one by one. Repeating the stored level and lowering it stay possible.
  • com.openexchange.deputy.provider.imap.fallbackSentFolderName is an operator override for the administrative provisioning path, which resolves the granting user's Sent folder without a session of that user. It is empty by default, relative to the personal namespace prefix, reloadable and config-cascade aware.

Clients detect availability through the new capability deputy_sent_folder, granted exactly while the property is true and, like deputy, never granted to guests.

New error codes, all raised before anything is stored: DEPUTY-0015 (access without a mail module permission), DEPUTY-0017 (access raised while the option is off), IMAP_DEPUTY-0016 and IMAP_DEPUTY-0017 (Sent folder not existent respectively not determinable), IMAP_DEPUTY-0018 (cross-context access without a DoveAdm connection or its namespace prefixes) and IMAP_DEPUTY-0019 (folder mode specific covering the Sent folder but not the INBOX while access is granted; checked on grants and on updates against the folders the permission currently covers).

Operator note: on the upgrade to 8.54, and only that one, a node still running the previous version can widen a deputy's rights on the granting user's Sent folder; deployments rule this out by enabling the property only once the rolling upgrade has completed on every node. With acl_defaults_from_inbox = yes, enabling the option replaces the inherited INBOX rights on that folder with the granted level.

See the feature documentation for the rolling-upgrade details, the required Dovecot settings and the folder resolution order.

SCR-1944

Summary: New configuration options for the MCP server

Effective: applies to the 8.54 release line

The MCP server endpoint /mcp is configured through these properties, read when the bundle com.openexchange.mcp starts and again on a configuration reload.

  • com.openexchange.mcp.enabled Whether the MCP endpoint /mcp and its resource metadata document /.well-known/oauth-protected-resource/mcp are registered. Default false. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.allowedOrigins Comma-separated Origin header values a browser-based client may send. A request with an Origin that is not listed is answered with 403. Default empty, which refuses every request carrying an Origin header. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.authorizationServers Comma-separated issuer URLs advertised as authorization_servers in the RFC 9728 resource metadata. Default empty; com.openexchange.oauth.provider.allowedIssuer is advertised then, if set. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.serverName The name reported as serverInfo.name. Default open-xchange-mcp. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.instructions The natural-language guidance a client receives from server/discover. Default: a sentence describing the read-only access to mail, calendar and contacts. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.rateLimit.calls How many tool calls a user may make per window, counted across all nodes through the rate limiter service; beyond that a call is answered with 429 and a Retry-After header. A value less than or equal to 0 (zero) disables the limit. Default 60. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.rateLimit.windowSeconds The window of the tool call limit in seconds. A value less than 1 is treated as 1. Default 60. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mcp.maxConcurrentCalls How many tool calls and resource reads of one user may run at the same time on a node; a further one is answered with 429 and Retry-After: 1, takes no permit of the rate limit and is audited with outcome busy. Per node, since a call in flight lives on the node that runs it. A value less than or equal to 0 (zero) switches the bound off. Default 4. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1830

Summary: Renamed configuration properties keep resolving under their previous names

Effective: applies to the 8.54 release line

A number of configuration properties were renamed to consistent, fully-qualified names, for example com.openexchange.hazelcast.network.join to com.openexchange.hazelcast.network.join.mode and the bare legacy key JMXServerPort to com.openexchange.jmx.serverPort. Deployments that still configure an old name are not affected: when a renamed property is not set under its new name, the server automatically falls back to the value configured under the previous name. If both names are set, the new name wins. The fallback is a transitional measure and will be removed in a future release, so configurations should be migrated to the new names; every value served through the fallback is reported in the server log, typically at startup, as Deprecated key <previous name> detected. See the property changes documentation for the complete list of renamed properties with previous name, new name and, where a bare previous name only applies within one file, that file.

SCR-1829

Summary: Migrated middleware configuration to typed properties with built-in defaults and removed 55 shipped .properties files

Effective: applies to the 8.54 release line

The middleware configuration is migrated to typed property definitions whose default values live inside the server. As a consequence, 55 .properties files that only carried default values are no longer shipped to /opt/open-xchange/etc. The built-in defaults are identical to the values those files used to ship, so effective configuration is unchanged and no operator action is required. Overriding a default works as before: set the property in any .properties file in the configuration directory (the server reads them all, file names do not matter) or through the config cascade. Files that are read by name (configdb.properties, system.properties, whitelist.properties, the OAuth provider files, AdminUser.properties, Group.properties, Resource.properties, permissions.properties, caldav.properties and others) are still shipped. See the property documentation for the authoritative defaults. The removed files are listed in the property changes documentation.

Database

SCR-1957

Summary: New Columns sentFolderAccess and sentFolderCovered in Table deputy for the Deputy Sent Folder Access

Effective: applies to the 8.54 release line

Warning

Update Task{{com.openexchange.deputy.impl.groupware.DeputyStorageAddSentFolderAccessColumnTask}}

{{com.openexchange.deputy.impl.groupware.DeputyStorageAddSentFolderCoveredColumnTask}}

The deputy table gains two columns for the deputy Sent folder access.

sentFolderAccess stores the level of access a deputy is granted to the granting user's standard Sent folder (0 none, 1 write, 2 readWrite). Behavior-neutral for existing rows: the default 0 keeps existing deputy permissions without Sent folder access.

sentFolderCovered records, after every mail grant or update, whether the module permission's folder set covers the granting user's standard Sent folder (1) or not (0). It tells a Sent folder covered by the folder mode apart from the Sent-specific grant of the option; the deputy metadata on the folder cannot serve that purpose, as the deputy may rewrite it. NULL means not recorded yet: existing rows get the value on their next mail grant or update and fall back to the stored folder mode until then.

ALTER TABLE deputy ADD COLUMN sentFolderAccess TINYINT UNSIGNED NOT NULL DEFAULT 0;
ALTER TABLE deputy ADD COLUMN sentFolderCovered TINYINT(1) DEFAULT NULL;

Fresh schemas: com.openexchange.deputy.impl.groupware.DeputyStorageCreateTableService. Existing schemas: com.openexchange.deputy.impl.groupware.DeputyStorageAddSentFolderAccessColumnTask and com.openexchange.deputy.impl.groupware.DeputyStorageAddSentFolderCoveredColumnTask (both idempotent, depending on DeputyStorageCreateTableTask). The deputy table is small on typical deployments, so no noticeable ALTER cost is expected.

Packaging/Bundles

SCR-2005

Summary: Package open-xchange-mobile-api ships push subscriptions and requires open-xchange-pns-impl

Effective: 8.54.320 and later

The package open-xchange-mobile-api now also ships the bundle com.openexchange.mobile.push, which manages the push subscriptions of Mobile API apps, and requires open-xchange-pns-impl. Installations that deploy the package need open-xchange-pns-impl as well; push credentials for the apps are configured as described in the Mobile API documentation.

SCR-1831

Summary: Consolidated configuration bundles into com.openexchange.config.common

Effective: applies to the 8.54 release line

The shared configuration types are reorganized into dedicated bundles. Newly introduced:

  • com.openexchange.config.common - contains the classic ConfigurationService, the reload types and the lean configuration API; the com.openexchange.config package moves here from com.openexchange.configread, which remains as the provider implementation.
  • com.openexchange.config.universal - the relocated user-configuration API.
  • com.openexchange.config.mapping - resolves renamed property keys to their previous names.
  • com.openexchange.admin.common, com.openexchange.sessiond.config and com.openexchange.timer - split out of their host bundles to break dependency cycles.

The bundle com.openexchange.config.lean is renamed to com.openexchange.config.lean.impl; the API package name com.openexchange.config.lean is unchanged. All affected packages are shipped as before; install lists that pin individual bundles must be updated accordingly.

8.54.325

API - HTTP-API

SCR-2014

Summary: New Mobile API operations for live events and batch requests

Effective: 8.54.322 and later; also delivered in 8.54.323, 8.54.324, 8.54.325

The Mobile API gets two operations:

  • GET /mobile/v1/events: live changes as Server-Sent Events. state events carry an EventPayload with the API's folder identifiers and the topics mail.message.* and mail.folder.changed. The stream needs scope read_mail, skips changes made by its own device and answers 404 where com.openexchange.pns.transport.sse.enabled is false for the user.

  • POST /mobile/v1/batch: up to 20 operations in one request, run in order. An entry can refer to results of earlier entries with $ref:<id>#<JSON pointer>. Each entry carries its own status; scopes and Idempotency-Key apply per entry.

At <dispatcher prefix>events, HEAD now answers 405 instead of opening a stream, and errors use the problem format of the Mobile API: each carries a {

}, e.g. {{invalid_token}}, and a {{type}} of {{https://documentation.open-xchange.com/mobile/v1/problems/<code>}}. Status codes and headers are unchanged.

8.54.324

API - HTTP-API

SCR-2014

Summary: New Mobile API operations for live events and batch requests

Effective: 8.54.322 and later; also delivered in 8.54.323, 8.54.324

The Mobile API gets two operations:

  • GET /mobile/v1/events: live changes as Server-Sent Events. state events carry an EventPayload with the API's folder identifiers and the topics mail.message.* and mail.folder.changed. The stream needs scope read_mail, skips changes made by its own device and answers 404 where com.openexchange.pns.transport.sse.enabled is false for the user.

  • POST /mobile/v1/batch: up to 20 operations in one request, run in order. An entry can refer to results of earlier entries with $ref:<id>#<JSON pointer>. Each entry carries its own status; scopes and Idempotency-Key apply per entry.

At <dispatcher prefix>events, HEAD now answers 405 instead of opening a stream, and errors use the problem format of the Mobile API: each carries a {

}, e.g. {{invalid_token}}, and a {{type}} of {{https://documentation.open-xchange.com/mobile/v1/problems/<code>}}. Status codes and headers are unchanged.

SCR-2013

Summary: New Mobile API endpoint for the message changes of a mail folder

Effective: 8.54.324 and later

The Mobile API gains GET /mobile/v1/mail/folders/{folderId}/message-changes, with which native mail apps fetch the messages created, updated and destroyed in a folder since a state taken from the folder list or from an earlier call. A call returns at most limit created and updated messages (default and maximum 500) and hasMore; include embeds fields of those messages, never their body. On IMAP the server needs CONDSTORE and QRESYNC; JMAP accounts are supported as well. A state that is too old, belongs to another folder or cannot be computed is answered with 410 and code cannot_calculate_changes. The endpoint needs the scope read_mail.

8.54.323

API - HTTP-API

SCR-2014

Summary: New Mobile API operations for live events and batch requests

Effective: 8.54.322 and later; also delivered in 8.54.323

The Mobile API gets two operations:

  • GET /mobile/v1/events: live changes as Server-Sent Events. state events carry an EventPayload with the API's folder identifiers and the topics mail.message.* and mail.folder.changed. The stream needs scope read_mail, skips changes made by its own device and answers 404 where com.openexchange.pns.transport.sse.enabled is false for the user.

  • POST /mobile/v1/batch: up to 20 operations in one request, run in order. An entry can refer to results of earlier entries with $ref:<id>#<JSON pointer>. Each entry carries its own status; scopes and Idempotency-Key apply per entry.

HEAD on <dispatcher prefix>events now answers 405 instead of opening a stream; the endpoint is otherwise unchanged.

Configuration

SCR-2009

Summary: New Configuration Property to Use PKCE in the OpenID Connect Authorization Code Flow

Effective: 8.54.323 and later

The new lean configuration property com.openexchange.oidc.pkceMethod enables Proof Key for Code Exchange (PKCE, RFC 7636) for the OpenID Connect authorization code flow. PKCE binds an authorization code to the authentication request, so that an intercepted code cannot be redeemed elsewhere. It defaults to an empty value, is reloadable and not config-cascade aware.

If set to a code challenge method, typically S256, a code challenge is sent with every authentication request, and the matching code verifier is presented when the authorization code is exchanged. The OpenID Provider can then be configured to require PKCE for the client. Method plain is discouraged, and an unsupported method leads to a failed login and a warning in the log. Set the property only once all middleware nodes are updated. No behavior change for existing deployments.

See the property documentation for further details.

SCR-2008

Summary: New Configuration Property to Bind OpenID Connect Logins to the Initiating Browser

Effective: 8.54.323 and later

The new lean configuration property com.openexchange.oidc.bindStateToBrowser binds an OpenID Connect authentication request to the browser that initiated it, which protects against login CSRF. It defaults to false, is reloadable and not config-cascade aware.

If enabled, the init request sets the short-lived cookie open-xchange-oidc-state-<state>, and the authentication callback is only accepted from a browser that presents it. The cookie is scoped to the host and path of com.openexchange.oidc.rpRedirectURIAuth, and is sent with SameSite=None; Secure for an https callback, so that the silent relogin inside an iframe still works. The init request therefore has to reach the same host as the callback. No behavior change for existing deployments.

See the property documentation for further details.

SCR-1978

Summary: New configuration options for live change events

Effective: 8.54.320 and later; also delivered in 8.54.322, 8.54.323

The new push notification transport sse delivers notifications to live change streams (Server-Sent Events). The following options control it.

  • com.openexchange.pns.transport.sse.enabled Whether the transport is enabled. It can be refined per client and topic by appending .<client> and .<client>.<topic>. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.pingInterval The interval in milliseconds in which an open stream refreshes its presence and sends a ping event to the client; a presence that is not refreshed expires after twice this interval. Default 30000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerUser The maximum number of streams a user may have open across the cluster, counted per endpoint. Streams at <dispatcher prefix>events and at /mobile/v1/events do not replace each other; at /mobile/v1/events the limit applies per device (access token, or Push-Subscription-Id in mode idp). Opening one more closes the oldest stream of the same endpoint and device with reason replaced. A value less than or equal to 0 (zero) disables the limit. Default 5. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerNode The maximum number of streams open on one node. Further requests are rejected with HTTP status 503 and a Retry-After header, so the client can reconnect to another node. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxLifetime The maximum lifetime of a stream in milliseconds. Once reached, the stream ends with reason lifetime and the client reconnects. A value less than or equal to 0 (zero) is ignored and the default is used. Default 1800000 (30 minutes). Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.tokenCheckInterval The interval in milliseconds in which the bearer token of a stream is validated again; a stream whose token was revoked or expired then ends with reason token_invalid. Keep it below 5 minutes, the time an idle OAuth session lives. A value less than or equal to 0 (zero) is ignored and the default is used. Default 120000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.mode How changes made outside the middleware, e.g. by another mail client, are noticed for live change streams of users whose primary account is IMAP. poll looks at the mailbox of a user with an open stream once per pollInterval, in one LIST "" "*" RETURN (STATUS (MESSAGES UIDNEXT UIDVALIDITY HIGHESTMODSEQ)) command on a pooled connection; it needs the IMAP extensions LIST-EXTENDED and LIST-STATUS, and CONDSTORE for changes that leave the message counts alone. off leaves such changes unnoticed. Changes made through the middleware are reported either way. Default poll. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.pollInterval The interval in milliseconds in which such a mailbox is looked at. Streams are handled every 5 seconds, so shorter values take effect as 5 seconds. Default 10000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.maxWatches The maximum number of users a node watches this way. Streams of further users still report the changes made through the middleware. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.liveChanges.echoWindow The time in milliseconds within which the mail server’s report of a change the middleware made itself is left out, so that a live change stream sees such a change once. It has to exceed the time the mail server needs to report a change, e.g. com.openexchange.imap.liveChanges.pollInterval. A value less than or equal to 0 (zero) switches the suppression off and reports the change twice. Default 15000. Reloadable, config-cascade aware. No dedicated properties file.

8.54.322

API - HTTP-API

SCR-2014

Summary: New Mobile API operations for live events and batch requests

Effective: 8.54.322 and later

The Mobile API gets two operations:

  • GET /mobile/v1/events: live changes as Server-Sent Events. state events carry an EventPayload with the API's folder identifiers and the topics mail.message.* and mail.folder.changed. The stream needs scope read_mail, skips changes made by its own device and answers 404 where com.openexchange.pns.transport.sse.enabled is false for the user.

  • POST /mobile/v1/batch: up to 20 operations in one request, run in order. An entry can refer to results of earlier entries with $ref:<id>#<JSON pointer>. Each entry carries its own status; scopes and Idempotency-Key apply per entry.

HEAD on <dispatcher prefix>events now answers 405 instead of opening a stream; the endpoint is otherwise unchanged.

SCR-2012

Summary: New Mobile API endpoints for mail folders

Effective: 8.54.321 and later; also delivered in 8.54.322

The Mobile API gains /mobile/v1/mail/folders, with which native mail apps read and manage the mail folders of all accounts. GET /mobile/v1/mail/folders returns a flat list with parentId, counts and, per folder, a state for message changes; on IMAP servers with LIST-STATUS and CONDSTORE the states of all folders of an account come from one command, and If-None-Match answers 304 while nothing changed. POST creates a folder and requires Idempotency-Key; GET, PATCH (application/merge-patch+json: rename, move, subscribe) and DELETE (to the trash unless permanent=true) work on /mobile/v1/mail/folders/{folderId}. Writes need If-Match (428 without it, 412 when stale); a name clash is answered with 409 and code folder_exists. Reads need the scope read_mail, writes write_mail. On Dovecot and JMAP a folder keeps its identifier when it is renamed or moved.

Configuration

SCR-1978

Summary: New configuration options for live change events

Effective: 8.54.320 and later; also delivered in 8.54.322

The new push notification transport sse delivers notifications to live change streams (Server-Sent Events). The following options control it.

  • com.openexchange.pns.transport.sse.enabled Whether the transport is enabled. It can be refined per client and topic by appending .<client> and .<client>.<topic>. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.pingInterval The interval in milliseconds in which an open stream refreshes its presence and sends a ping event to the client; a presence that is not refreshed expires after twice this interval. Default 30000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerUser The maximum number of streams a user may have open across the cluster, counted per endpoint. Streams at <dispatcher prefix>events and at /mobile/v1/events do not replace each other; at /mobile/v1/events the limit applies per device (access token, or Push-Subscription-Id in mode idp). Opening one more closes the oldest stream of the same endpoint and device with reason replaced. A value less than or equal to 0 (zero) disables the limit. Default 5. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerNode The maximum number of streams open on one node. Further requests are rejected with HTTP status 503 and a Retry-After header, so the client can reconnect to another node. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxLifetime The maximum lifetime of a stream in milliseconds. Once reached, the stream ends with reason lifetime and the client reconnects. A value less than or equal to 0 (zero) is ignored and the default is used. Default 1800000 (30 minutes). Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.tokenCheckInterval The interval in milliseconds in which the bearer token of a stream is validated again; a stream whose token was revoked or expired then ends with reason token_invalid. Keep it below 5 minutes, the time an idle OAuth session lives. A value less than or equal to 0 (zero) is ignored and the default is used. Default 120000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.mode How changes made outside the middleware, e.g. by another mail client, are noticed for live change streams of users whose primary account is IMAP. poll looks at the mailbox of a user with an open stream once per pollInterval, in one LIST "" "*" RETURN (STATUS (MESSAGES UIDNEXT UIDVALIDITY HIGHESTMODSEQ)) command on a pooled connection; it needs the IMAP extensions LIST-EXTENDED and LIST-STATUS, and CONDSTORE for changes that leave the message counts alone. off leaves such changes unnoticed. Changes made through the middleware are reported either way. Default poll. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.pollInterval The interval in milliseconds in which such a mailbox is looked at. Streams are handled every 5 seconds, so shorter values take effect as 5 seconds. Default 10000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.maxWatches The maximum number of users a node watches this way. Streams of further users still report the changes made through the middleware. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.liveChanges.echoWindow The time in milliseconds within which the mail server’s report of a change the middleware made itself is left out, so that a live change stream sees such a change once. It has to exceed the time the mail server needs to report a change, e.g. com.openexchange.imap.liveChanges.pollInterval. A value less than or equal to 0 (zero) switches the suppression off and reports the change twice. Default 15000. Reloadable, config-cascade aware. No dedicated properties file.

8.54.321

API - HTTP-API

SCR-2012

Summary: New Mobile API endpoints for mail folders

Effective: 8.54.321 and later

The Mobile API gains /mobile/v1/mail/folders, with which native mail apps read and manage the mail folders of all accounts. GET /mobile/v1/mail/folders returns a flat list with parentId, counts and, per folder, a state for message changes; on IMAP servers with LIST-STATUS and CONDSTORE the states of all folders of an account come from one command, and If-None-Match answers 304 while nothing changed. POST creates a folder and requires Idempotency-Key; GET, PATCH (application/merge-patch+json: rename, move, subscribe) and DELETE (to the trash unless permanent=true) work on /mobile/v1/mail/folders/{folderId}. Writes need If-Match (428 without it, 412 when stale); a name clash is answered with 409 and code folder_exists. Reads need the scope read_mail, writes write_mail. On Dovecot and JMAP a folder keeps its identifier when it is renamed or moved.

8.54.320

3rd Party Libraries/License Change

SCR-1920

Summary: Upgraded the Equinox OSGi framework from 3.24.200 to 3.24.300

Effective: 8.54.320 and later

Upgraded the Equinox OSGi framework in the target platform from 3.24.200 to 3.24.300. The bundle symbolic name and all exported packages are unchanged; the jar is the upstream artifact, byte-identical to the one published on Maven Central.

The file name carries the bundle version, so every place that names it explicitly moves with it:

  • oxscripts/sbin/open-xchange.in and open-xchange-cloud.in — the OSGI_JAR the service scripts start,
  • docker/go/open-xchange/core/entrypoint/core/start.go — the same path in the container entry point,
  • openexchange-test/.classpath and com.openexchange.bundles/3rdPartyLibs.properties.

The obsolete org.osgi.annotation 6.0.0 bundle is removed alongside. It dates from 2014 and contains only the four annotations of org.osgi.annotation.versioning, which the platform already ships properly in org.osgi.annotation.versioning 1.1.2. No operator action is required.

SCR-1913

Summary: Upgraded the Bean Validation API from 1.1.0.Final to 2.0.1.Final

Effective: 8.54.320 and later

Upgraded the Bean Validation API in the target platform from 1.1.0.Final to 2.0.1.Final. The bundle symbolic name stays javax.validation.api, but the eight exported packages now carry version 2.0.1.Final, and javax.validation.valueextraction is exported in addition.

The upgrade is additive: no class and no member was removed between the two versions, 37 classes were added, and ConstraintValidator.initialize changed from abstract to a default method. Every consumer that imports these packages resolves against 2.0.1 as well — the ranges in use are [1.1,3), [2.0,3) and [2.0.0,3.0.0), plus imports without a range.

It also closes an existing gap. App Suite Office compiles against Bean Validation 2.0.2, which it ships itself, and uses types that 1.1.0 does not contain at all (for example ClockProvider and NotBlank), while its bundles were wired to the platform's 1.1.0 export at runtime.

No configuration change and no operator action are required.

SCR-1905

Summary: Upgraded Apache Tika from 3.3.1 to 4.0.0

Effective: 8.54.320 and later

Upgraded tika-core from 3.3.1 to 4.0.0 in bundle com.openexchange.tika.util. Tika stays fully encapsulated — the bundle exports only com.openexchange.tika.util — so the major upgrade is invisible to consumers and MIME type detection is unchanged.

The 4.0 API required three adjustments inside the wrapper:

  • TikaConfig was replaced by a JSON-based configuration. The parameterless Tika() constructor yields the same defaults the removed Tika(TikaConfig) call produced.
  • Detector.detect now takes a TikaInputStream and a ParseContext instead of a plain InputStream.
  • The HTTP header constants moved from Metadata to HttpHeaders and changed from String to Property.

Tika 4 declares commonmark as a dependency for its Markdown output handler, which this bundle never uses, so those three jars are not embedded. No operator action is required.

SCR-1899

Summary: Upgraded eight third-party libraries to their latest patch or minor release

Effective: 8.54.320 and later

Upgraded eight third-party libraries to their latest patch or minor release. No API or configuration change; no operator action is required.

  • SLF4J and its JCL, JUL, log4j and OSGi bridges from 2.0.18 to 2.0.19 in the target platform.
  • Micrometer from 1.17.0 to 1.17.1 in com.openexchange.metrics.micrometer (also raises the embedded micrometer-commons and micrometer-observation).
  • Spring Beans and Spring Core from 7.0.8 to 7.0.9 in com.openexchange.xml.
  • httpclient5-cache from 5.6.2 to 5.6.4 in com.openexchange.saml, matching the HttpClient 5 version the target platform ships.
  • CBOR from 4.5.2 to 4.5.6 in com.openexchange.webauthn, matching com.openexchange.multifactor.provider.webauthn. The release moved its string utilities into datautilities, which is now embedded alongside it.
  • JavaCC from 7.0.12 to 7.0.13, Mockito from 3.12.4 to 5.23.0 and Jackson Datatype Joda from 2.11.0 to 2.22.2 — build- and test-time only, not shipped.

SCR-1896

Summary: Upgraded the embedded S3 encryption client from 3.6.1 to 4.0.2

Effective: 8.54.320 and later

Upgraded amazon-s3-encryption-client-java from 3.6.1 to 4.0.2 in bundle software.amazon.awssdk.

  • amazon-s3-encryption-client-java-3.6.1.jar is replaced by amazon-s3-encryption-client-java-4.0.2.jar; no exported package changes.
  • Version 4.0 removes S3EncryptionClient.builder(). The client is now created via builderV4() with the commitment policy pinned to FORBID_ENCRYPT_ALLOW_DECRYPT and the algorithm suite to ALG_AES_256_GCM_IV12_TAG16_NO_KDF.

The stored object format is unchanged: objects written before the upgrade stay readable, newly written objects stay readable by earlier App Suite versions, and no data migration is required. Without the pinned settings the 4.0 default of key commitment with REQUIRE_ENCRYPT_REQUIRE_DECRYPT would reject every previously written encrypted object.

SCR-1895

Summary: Upgraded the embedded SAAJ implementation from 2.0.1 to 3.0.6

Effective: 8.54.320 and later

Upgraded the embedded SAAJ implementation in software.amazon.awssdk from 2.0.1 to 3.0.6, the version that com.openexchange.soap.common already ships. Both bundles now embed the same version.

The jar is embedded because the S3 encryption client uses com.sun.xml.messaging.saaj.packaging.mime.internet.MimeUtility to decode content metadata; nothing else in the bundle touches SAAJ. That class has an identical public API in both versions and depends only on jakarta.activation, which the platform provides. No operator action is required.

SCR-1894

Summary: Upgraded the Kotlin OSGi bundle from 2.4.10 to 2.4.20

Effective: 8.54.320 and later

Upgraded the Kotlin OSGi bundle in the target platform from 2.4.10 to 2.4.20, replacing kotlin-osgi-bundle-2.4.10.jar with kotlin-osgi-bundle-2.4.20.jar.

The bundle keeps its symbolic name org.jetbrains.kotlin.osgi-bundle; the exported packages move from version 2.4.10 to 2.4.20 and gain kotlin.coroutines.debug. No package was removed. The two consuming bundles import the Kotlin packages without a version range, so no operator action is required.

SCR-1893

Summary: Upgraded embedded third-party libraries in 20 bundles

Effective: 8.54.320 and later

Upgraded embedded third-party libraries in 20 bundles. All are minor or patch releases within the same major version.

  • AWS SDK for Java 2.46.21 to 2.55.1 in software.amazon.awssdk

  • Kubernetes Client 7.8.0 to 7.9.0 in io.fabric8.kubernetes

  • Guava 33.6.0-jre to 33.7.1-jre and Caffeine 3.2.4 to 3.3.0 in com.google.guava

  • Nimbus OAuth 2.0 SDK 11.37.2 to 11.38.2 in com.nimbus

  • SnakeYAML 2.6 to 2.7 in org.yaml.snakeyaml

  • libphonenumber 9.0.34 to 9.0.39 in com.openexchange.sms

  • Liquibase 5.0.3 to 5.0.4 in liquibase.core

  • GeoIP2 5.1.0 to 5.2.0 and MaxMind DB 4.1.0 to 4.2.0 in com.openexchange.geolocation.maxmind.binary; MaxMind DB 4.2.0 rejects crafted databases beyond new decoder limits, regular lookups stay far below them

  • Box Java SDK 10.15.1 to 10.17.0 in com.openexchange.file.storage.boxcom; commons-codec, now required by the SDK, is imported from the target platform

  • Jolokia 2.6.0 to 2.6.3 in com.openexchange.jolokia

  • prometheus-metrics 1.7.0 to 1.9.0 in com.openexchange.metrics.micrometer, now at one version across all embedded prometheus-metrics-* jars

  • and further minor and patch updates to metadata-extractor, Dropbox SDK, Woodstox, Dropwizard Metrics, jTNEF, Xalan serializer, brownies-collections and jackson-datatype-joda

The jar file names listed in each bundle's Bundle-ClassPath and 3rdPartyLibs.properties change accordingly. No artifact was added to or removed from any embed set, and no operator action is required.

SCR-1891

Summary: Upgraded the embedded TwelveMonkeys, Apache CXF, Google API Client, Jetty and OkHttp libraries

Effective: 8.54.320 and later

Upgraded the third-party libraries embedded in five bundles.

  • TwelveMonkeys ImageIO 3.13.1 to 3.15.2 in com.openexchange.imagetransformation.java; 3.15.1 and 3.15.2 harden the IFF, PICT and PSD decoders against crafted files
  • Apache CXF 4.2.2 to 4.2.3 in com.openexchange.soap.common
  • Google API Client in com.google.api.client: google-http-client 2.1.1 to 2.2.0, google-api-client 2.9.0 to 2.9.1, google-auth-library-oauth2-http 1.47.0 to 1.52.0, api-common 2.65.0 to 2.68.0, and the Calendar, Drive and Gmail service APIs
  • Jetty 12.1.12 to 12.1.13 in com.openexchange.http.jetty
  • OkHttp 5.4.0 to 5.5.0 in com.squareup.okhttp3, which also updates the embedded okio-jvm from 3.17.0 to 3.18.1

The jar file names listed in each bundle's Bundle-ClassPath and 3rdPartyLibs.properties change accordingly. The newer Google libraries add jspecify-1.0.0.jar as a further embedded artifact. No operator action is required.

SCR-1870

Summary: Upgraded Jackrabbit WebDAV from 2.21.19 to 2.22.4 in target platform

Effective: 8.54.320 and later

The Jackrabbit WebDAV client library (jackrabbit-webdav) is upgraded from 2.21.19 to 2.22.4 in the target platform. Our copy stays re-wrapped: the bundle's Import-Package range for javax.servlet and javax.servlet.http is widened from the upstream [3.1,4) to [4,5), because the platform exports javax.servlet 4.0.0, which the upstream range excludes. Exported packages and their versions are unchanged, and the library is used internally by the WebDAV file storage and DAV subscription bundles only. No admin action.

SCR-1869

Summary: Upgraded Logback from 1.5.38 to 1.6.3 in target platform

Effective: 8.54.320 and later

Logback (logback-classic, logback-core) is upgraded from 1.5.38 to 1.6.3 in the target platform. The only configuration element that disappears is the long deprecated ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter. The configurations shipped with the middleware do not use it, but a logback.xml that still declares it as a <turboFilter> has to drop that element and rely on the scan attribute instead. No other Joran action, model or appender was removed. No admin action otherwise.

SCR-1867

Summary: Upgraded MySQL Connector/J from 9.7.0 to 26.7.0 in target platform

Effective: 8.54.320 and later

Upgraded the MySQL JDBC driver in the target platform (com.openexchange.bundles): mysql-connector-j 9.7.0 to 26.7.0.

The jump in the version number is a change of the upstream versioning scheme, not a rewrite: 26.7.0 is the release that directly follows 9.7.0, with no version in between. Bundle symbolic name (com.mysql.cj), the 27 exported packages and the JDBC driver class (com.mysql.cj.jdbc.Driver) are unchanged, and every consumer imports the driver packages without a version range, so the exported package versions moving from 9.7.0 to 26.7.0 affects nobody. No source change was required.

Compatibility with MariaDB — the only supported database — was verified against MariaDB 10.11.19: reading (folders, mail, calendar, contacts, capabilities, quota) and a full write cycle (insert, select, update, delete of a task) both work, with no SQL exception of any kind in the log.

SCR-1866

Summary: Upgraded Kotlin OSGi bundle and Equinox Configuration Admin in target platform, added the OSGi Coordinator API

Effective: 8.54.320 and later

Upgraded two libraries in the target platform (com.openexchange.bundles):

  • kotlin-osgi-bundle 2.2.21 to 2.4.10
  • org.eclipse.equinox.cm 1.4.100 to 1.6.400

Equinox Configuration Admin 1.6.x adds a mandatory import of org.osgi.service.coordinator;version="[1.0.0,2.0.0)", which no bundle in the target platform provided. The OSGi Coordinator API bundle org.osgi.service.coordinator 1.0.2 is therefore added alongside it; without it the org.eclipse.equinox.cm bundle would not resolve.

The Kotlin upgrade moves the exported package versions from 2.2.21 to 2.4.10 for all 116 packages. That is safe here because every consumer -- com.squareup.okhttp3 and com.openexchange.jmap -- imports the Kotlin packages without a version range.

SCR-1865

Summary: Upgraded commons-collections4, commons-validator, javassist, jctools, joda-time, jakarta.validation-api, xmlunit, protobuf-java, HK2, Logback, SPI Fly and Apache mime4j in target platform

Effective: 8.54.320 and later

Routine upgrades of third-party libraries in the target platform (com.openexchange.bundles). No source change was required.

  • commons-collections4 4.5.0 to 4.6.0
  • commons-validator 1.10.1 to 1.11.0
  • javassist 3.32.0-GA to 3.33.0-GA
  • jctools-core 4.0.6 to 4.0.7
  • joda-time 2.14.2 to 2.14.4
  • jakarta.validation-api 3.1.0 to 3.1.1
  • xmlunit-core 2.10.0 to 2.14.0
  • protobuf-java 4.35.1 to 4.36.2
  • hk2-api, hk2-locator, hk2-utils and aopalliance-repackaged 4.0.1 to 4.0.2
  • glassfish-corba-omgapi 5.0.0 to 5.0.2
  • logback-classic and logback-core 1.5.37 to 1.5.38; both keep importing org.slf4j;version="[2.0,3)", so SLF4J stays at 2.0.18
  • org.apache.aries.spifly.dynamic.bundle 1.3.7 to 1.3.8; it now requires ASM 9.10 and OSGi Core R8, both provided by the target platform
  • apache-mime4j-core, apache-mime4j-dom and apache-mime4j-storage 0.8.14 to 0.8.15

As before, glassfish-corba-omgapi is re-wrapped so that the specification packages javax.rmi and javax.rmi.CORBA are exported at the specification version 1.0.0 rather than at the bundle version. Without that, jdo-api cannot satisfy its javax.rmi;version="[1.0,2)" import and the javax.jdo bundle stops resolving.

Apache mime4j 0.8.15 adds default parsing limits: at most 512 MIME parts, a nesting depth of 64, 16384 header fields and 1 MiB of header data per message. Mail import (mail?action=import) parses strictly by default, so a message beyond these limits is now rejected with MSG-0101; with strictParsing=false it is still imported.

SCR-1864

Summary: Upgraded BouncyCastle, Jackson, PDFBox, jsoup, HttpClient5, FreeMarker, Commons Codec and snappy-java in target platform

Effective: 8.54.320 and later

Upgraded third-party libraries in the target platform (com.openexchange.bundles):

  • bcmail-jdk18on, bcpg-jdk18on, bcpkix-jdk18on and bcutil-jdk18on upgraded from 1.84 to 1.86, bcprov-jdk18on from 1.84 to 1.86

  • jackson-core, jackson-databind, the jackson-dataformat-*, jackson-datatype-*, jackson-jakarta-rs-* and jackson-module-* jars upgraded from 2.22.0 to 2.22.2 (jackson-annotations stays at 2.22, no patch release exists)

  • pdfbox, pdfbox-io, fontbox and xmpbox upgraded from 3.0.7 to 3.0.8

  • jsoup upgraded from 1.22.2 to 1.23.2

  • httpclient5 upgraded from 5.6.2 to 5.6.4; httpcore5 stays at 5.4.3, the version HttpClient 5.6.4 is built against

  • freemarker upgraded from 2.3.34 to 2.3.35

  • commons-codec upgraded from 1.22.0 to 1.22.1

  • snappy-java upgraded from 1.1.10.7 to 1.1.10.8

BouncyCastle 1.85 removed the sample-code packages org.bouncycastle.crypto.examples, org.bouncycastle.mail.smime.examples and org.bouncycastle.openpgp.examples, and removed org.bouncycastle.iana; org.bouncycastle.asn1.iana moved from bcutil to bcprov. The stale (unused) imports of org.bouncycastle.crypto.examples and org.bouncycastle.iana were removed from the com.openexchange.saml bundle manifest. FreeMarker 2.3.35 dropped freemarker.debug and freemarker.debug.impl, which no bundle imports. No source change was required.

BouncyCastle 1.86 fixes 13 CVEs, among them OpenPGP subkey certification and SEIPDv1 truncation, CMS AuthenticatedData and the NameConstraints check on end-entity certificates in PKIXCertPathReviewer. It introduces default limits: S/MIME nesting depth 64 (org.bouncycastle.mime.max_depth), PBKDF2 iteration count 10,000,000 (org.bouncycastle.pbe.max_iteration_count) and 262,144 nodes when building certificate paths (org.bouncycastle.x509.max_cert_path_build_nodes). It also removes legacy post-quantum packages from bcprov such as org.bouncycastle.pqc.crypto.mlkem and org.bouncycastle.pqc.jcajce.provider.kyber; no bundle imports them.

SCR-1863

Summary: Updated Netty libraries from v4.2.16 to v4.2.18 in bundle io.netty and Lettuce from v7.6.0 to v7.7.0 in bundle io.lettuce

Effective: 8.54.320 and later

Upgraded Netty from v4.2.16.Final to v4.2.18.Final and netty-tcnative-classes from v2.0.80.Final to v2.0.84.Final in bundle io.netty, and Lettuce from v7.6.0.RELEASE to v7.7.0.RELEASE together with reactor-core from v3.8.6 to v3.8.7 in bundle io.lettuce. The Netty update fixes CVE-2026-59903, a cache poisoning and information disclosure issue in netty-codec-http, as well as several further security advisories affecting netty-codec-http and netty-codec-http2 (resource exhaustion, header validation, request smuggling). Bundle io.lettuce now also exports the new packages io.lettuce.core.probabilistic and io.lettuce.core.probabilistic.arguments; no packages were removed. No admin action.

API - HTTP-API

SCR-1995

Summary: New Mobile API for native mail apps at /mobile/v1

Effective: 8.54.320 and later

The middleware serves a resource-oriented HTTP API for native mail apps at /mobile/v1, described by its own OpenAPI document. This release brings the foundation every operation builds on; the mail operations follow.

  • Every request carries a bearer token. A deployment signs in either with personal access tokens (oxa_…) or with the operator's identity provider, never with both; a token of the other kind is answered with 401.
  • GET /mobile/v1/auth/config needs no token and tells a client which of the two modes applies and what it needs for it.
  • Errors are application/problem+json (RFC 9457) with a stable {
} clients switch on, the middleware's own error code as {{oxCode}} and the request's tracking identifier as {{instance}}.
* Every {{GET}} also answers {{HEAD}}. An unknown path is {{404}}, a known path with another method {{405}} with {{Allow}}.
* {{If-None-Match}} answers {{304}}. The write operations that follow will answer {{428}} without {{If-Match}} and {{412}} with the current representation when it is stale.

The API answers only where {{com.openexchange.mobile.api.enabled}} is set, and it needs the OAuth provider to validate tokens. Existing endpoints are unaffected.

SCR-1993

Summary: New header Idempotency-Key and parameter clientMessageId to send a mail at most once

Effective: 8.54.320 and later

The requests POST /mail?action=new, POST /mail/compose/{id}/send and PUT /mail?action=transport accept the header Idempotency-Key: a retry with the same key is not performed again, but answered with the kept result of the first request and the response header Idempotent-Replayed: true. A client can also pick a UUID per mail and pass it as clientMessageId when opening the composition space (POST /mail/compose), when sending, or as field of mail?action=new. The Message-ID header is built from it, and a later send of the same mail returns the first result with the response field duplicate set to true. New error codes IDEM-0001 to IDEM-0007; the REST API answers them with status 400, 409 or 422. Purely additive; requests without the header or the parameter behave as before.

SCR-1991

Summary: New login actions accessToken and accessTokenSecondFactor to sign in for a personal access token

Effective: 8.54.320 and later

POST <dispatcher prefix>login?action=accessToken lets a client without a browser sign in with user name and password (JSON body with login, password and the optional label, scopes and expires) and answers like accesstoken?action=new: with the token's metadata and, once, its secret. The sign-in runs the regular login with a transient session; no cookie is set and no session remains. App passwords and guest credentials are refused. A user with a second factor gets the error ACCESSTOKEN-0012 with a challenge (challengeId, expiresAt, factors) as data and confirms it with POST <dispatcher prefix>login?action=accessTokenSecondFactor (challengeId, provider, deviceId, secret_code) without sending the password again; only authenticator apps, backup strings and SMS are offered. Attempts are limited per login name, client address and user (LGI-0028, ACCESSTOKEN-0017). Both actions are off unless com.openexchange.accesstoken.signin.enabled is set; other bundles get the same sign-in through the OSGi service AccessTokenSignInService. Purely additive.

SCR-1977

Summary: New parameter pushToken for changing mail requests

Effective: 8.54.320 and later

Changes to mail and mail folders made through the middleware are now published as push notifications with the new topics ox:mail:changed, ox:mail:deleted and ox:mail:folder. Existing subscriptions to * or ox:mail:* receive them, too. To keep a client from being notified about its own changes, the following requests accept the new optional query parameter pushToken, the client's push token; the subscription with this token is skipped.

  • PUT /mail?action=update
  • PUT /mail?action=flags
  • PUT /mail?action=color_label
  • PUT /mail?action=copy
  • PUT /mail?action=copy_multiple
  • PUT /mail?action=move
  • PUT /mail?action=move_all
  • PUT /mail?action=delete
  • PUT /mail?action=expunge
  • PUT /mail?action=clear
  • POST /mail?action=new and PUT /mail?action=new
  • PUT /mail?action=autosave

The folders requests new, update, delete and clear already accept pushToken; it now applies to mail folder notifications as well. Requests without the parameter behave as before.

See the HTTP API documentation for further details.

SCR-1974

Summary: New capabilities access_tokens and mcp

Effective: 8.54.320 and later

Two new capabilities tell a client what to offer before it calls anything.

access_tokens is awarded to a user who may mint personal access tokens: the user is neither a guest nor anonymous, and the OAuth provider is enabled for the user. These are the checks the accesstoken module applies before minting, so a settings page shown only with this capability never offers what the module would refuse.

mcp is awarded to a user who can use the MCP endpoint: the endpoint is registered on the node, the user is neither a guest nor anonymous, and the OAuth provider is enabled for the user. It is independent of access_tokens, since personal access tokens serve more than the MCP endpoint and an MCP client may also bring a token from an external authorization server.

No admin action. access_tokens follows com.openexchange.oauth.provider.enabled; mcp also follows com.openexchange.mcp.enabled and whether the open-xchange-mcp package is installed.

SCR-1946

Summary: New HTTP API module accesstoken for personal access tokens

Effective: 8.54.320 and later

The new module accesstoken lets a user mint bearer tokens for clients that cannot run an OAuth flow, such as a script or an MCP client with a fixed Authorization header: new (parameters label, scopes, expires) returns the token metadata and, once, the secret oxa_<context-id>_<random>; all lists the user's tokens together with the scopes the user may grant (grantable_scopes, each as scope and description); delete revokes one. The actions require a full session of a regular user: a session made from a token cannot mint tokens, and guest users cannot mint tokens at all (ACCESSTOKEN-0004). The module is registered only while the OAuth provider is enabled, and minting is refused where the provider is disabled for the user (ACCESSTOKEN-0005). The bundles com.openexchange.accesstoken (API), com.openexchange.accesstoken.impl (storage, validator plug-in, clean-up, password-change handler, mail guard) and com.openexchange.accesstoken.json (the module) ship in the package open-xchange-core, since the tokens are a bearer credential for every OAuth-protected endpoint.

A token is bound to its user and limited to the scopes chosen at minting. The grantable scopes are the ones the installed modules register with the OAuth provider (OAuthScopeProvider, e.g. read_contacts, write_contacts, read_mail, read_calendar), offered only where the user's capabilities admit the module. Two limits apply, both configurable (com.openexchange.accesstoken.maxLifetimeDays, default 365, and com.openexchange.accesstoken.maxTokensPerUser, default 50). The token is accepted as bearer token by every endpoint that validates tokens through the OAuth provider; the HTTP API modules with restricted actions take it in place of the session parameter. A request outside the token's scopes is 403 with insufficient_scope; an unknown or revoked token is 401 with invalid_token, an expired one 401 with a description naming the expiry; when the token cannot be checked because the database is unavailable, the answer is 503 with temporarily_unavailable and the client keeps its token.

For mail access the token stores no credential of its own: minting asks the mail credential vault (feature access-token) to keep what the session provides, sealed with the token's secret, and the listing reports whether it did as mail_access. Under master authentication (com.openexchange.mail.passwordSource=global) the vault records that nothing is needed, so mail_access is true; a session without password or OAuth tokens mints a token with mail_access false, and such a token never reaches the mail server. A password change the user makes through App Suite revokes every token of the user; a password set through provisioning does not, since that path posts no password-change event. A token minted without mail credential is refused at the mail server with ACCESSTOKEN-0008 instead of presenting its secret there. Expired tokens are dropped by a clean-up job a week after their expiry.

SCR-1936

Summary: Soft-Deleted Attendees Marked with a Read-Only Extended Parameter in the Calendar HTTP API

Effective: 8.54.320 and later

The calendar HTTP API now marks a soft-deleted user (pending deletion) that appears as an attendee of an existing appointment, so a client can tell a leaver apart from an ordinary external participant.

When another participant loads an event a soft-deleted user is invited to, the user is resolved to an external attendee: the display name and address are kept, but the internal entity reference is dropped (the appointment data itself is not modified). Such an attendee now carries the read-only extended parameter X-OX-SOFT-DELETED in its extendedParameters object, whose value is the time of the soft-deletion in milliseconds since the epoch (UTC).

The marker is applied on read only: it is never written to the storage, and it is left out of exported iCal data, so it does not reach CalDAV clients. It is surfaced through the existing extendedParameters field, so no new attribute is introduced in the schema.

The organizer of an event keeps its internal reference and is therefore not marked - this preserves the ability to hand an appointment to another user through the change-organizer operation; the organizer's soft-deleted state stays resolvable through the user's soft_deleted attribute (SCR-1933).

See the Soft-deleted users documentation, as well as the HTTP API documentation for further details.

SCR-1933

Summary: Soft-deleted user state visible in the HTTP API

Effective: 8.54.320 and later

The user module renders the new attribute soft_deleted (column 628), the time a user has been soft-deleted in milliseconds since the epoch (UTC, not shifted by the user's time zone); absent for every other user. Soft-deleted users stay absent from users?action=all and users?action=search, a users?action=get with the identifier answers with the attribute set. A login of a soft-deleted user is refused with AUTHORIZATION-0004 ("The account is disabled and pending deletion") instead of AUTHORIZATION-0001. Lets a client tell a leaver apart from a disabled account.

SCR-1887

Summary: New Attribute classifiedAccess for Deputy Permissions to See Confidential Appointments

Effective: 8.54.320 and later

A granting user can now allow a deputy to see appointments marked as confidential in the shared calendar folders with all details, independently of the deputy attending them. The deputy permission gained the optional attribute classifiedAccess next to sendOnBehalfOf, accepted by PUT /ajax/deputy?action=new and PUT /ajax/deputy?action=update and reported back by action=get, action=all and action=reverse. Like sendOnBehalfOf it is an attribute of the whole deputy permission, but it exclusively concerns the calendar:

  • none (default) - classified appointments are anonymized (confidential) or hidden (private), as for any other user the calendar is shared with
  • confidential - appointments marked as confidential are shown with all details

Appointments marked as private always stay hidden from deputies.

Example request:

{
  "userId": 4,
  "folderMode": "default",
  "classifiedAccess": "confidential",
  "modulePermissions": {
    "calendar": {
      "permission": 257
    }
  }
}

The right applies to the calendar folders covered by the deputy permission. Together with the granted folder permission, the deputy may also edit and delete confidential appointments, classify appointments as confidential or back as public, and create confidential appointments in those folders; attachments as well as the details of confidential appointments in free/busy results and conflict checks follow the same rule, and the appointments keep their private / confidential event flags. On update, the attribute is only changed when present in the request, so the right is revoked by sending none explicitly. A level that cannot be granted is rejected with DEPUTY-0014, an unknown value with SVL-0010. The attribute is omitted in responses when not granted, so existing grants serialize as before.

Purely additive - existing requests and grants are unaffected. Known limitation: granting or revoking the right modifies no appointment, so clients synchronizing the shared calendar incrementally (action=updates, CalDAV, EAS) only pick up the changed visibility with their next full synchronization.

See the feature documentation and the HTTP API documentation for further details.

SCR-1878

Summary: New vacation rule fields "vacationMode", "subjectExt" and "textExt" to treat internal and external senders differently

Effective: 8.54.320 and later

The vacation action command of the mail filter v2 HTTP API (mailfilter/v2?action=new and action=update) gains fields that let the vacation notice answer internal and external senders differently:

  • vacationMode — the explicit mode: all (every sender receives the same notice — the classic rule), split (external senders receive the separate external text/subject) or internalOnly (only internal senders receive a notice)

  • textExt / subjectExt — the text and subject for external senders in mode split; textExt is required by that mode, and without subjectExt external senders get the auto-generated subject of RFC 5230, never the internal one

{"actioncmds":[{"id":"vacation","days":"7","vacationMode":"split","subject":"Out of office","text":"Back in 10 days.","subjectExt":"Out of office","textExt":"Your message will be forwarded.","from":["anton@example.com"]}]}

The client contract on vacationMode: clients supporting the new fields MUST send it on every write of a vacation action — including when the feature is not announced. all is always accepted, and an existing extended rule stays editable with its modes: while the feature is unavailable, clients echo the mode they read and offer the downgrade to all only as an explicit user choice with a warning — availability can also flip temporarily on a configuration mistake, and a routine edit must not degrade the rule. A vacation action WITHOUT vacationMode is treated as written by a client unaware of the fields: an existing internal/external structure of the rule is preserved across the otherwise complete replace instead of being silently stripped. The contract only holds if "always" means always — a client that stops sending the field once vacationInternalAvailable turns false leaves extended rules of its users in place instead of sunsetting them. On read, every vacation rule reports its vacationMode (all on classic notices too, pre-existing rules included) — sole exception are multi-branch rules the middleware does not recognize as extended vacation rules: they report no mode and carry an errormsg instead, see below; textExt/subjectExt are present exactly when set. Invalid combinations and values are rejected with MAIL_FILTER-0044: split without textExt, all/internalOnly combined with the external fields, an unknown mode value, external fields without vacationMode, and a subjectExt containing line breaks. A rule carrying more than one vacation action is rejected with the new MAIL_FILTER-0045.

Whether a sender is internal or external is decided by the classifier header the mail platform stamps on every delivered message (see the accompanying Configuration SCR). The feature is an operator opt-in; its availability is announced in the mail filter config action as vacationInternalAvailable, and requests introducing the extended modes while it is unavailable are rejected with MAIL_FILTER-0043. Updates of multi-branch rules the middleware does not recognize as extended vacation rules are rejected on both interfaces: the v2 interface refuses with MAIL_FILTER-0042 when the rule carries a vacation action and MAIL_FILTER-0041 otherwise (the legacy v1 interface makes a similar split, keyed on the rule's vacation flag), and the v2 list action announces the refusal up front by reporting such rules with an errormsg in the matching wording (the rule and its leading branch stay reported), so clients can render them read-only instead of failing on save. The v1 interface additionally rejects every extended vacation rule with MAIL_FILTER-0042, pointing the user to an up-to-date client (they stay fully editable through v2). See the accompanying Java-API SCR for the underlying parser change.

The pre-existing subject field now rejects line breaks the same way the new subjectExt does (MAIL_FILTER-0044): a line break used to be written verbatim into the Subject: header of the generated reply — saves that used to pass with a multi-line subject now fail.

Known limitation: Exchange ActiveSync devices read and set the out-of-office state through the legacy v1 interface (USM), whose vacation model cannot represent the extended two-branch structure — for a split/internalOnly notice such a device shows OOF as switched off, and setting OOF from the device fails. Editing the notice through the v2 interface is unaffected.

Existing clients are otherwise unaffected: without the new fields the produced Sieve rule is unchanged, and the additions to the JSON returned on read are the vacationMode field (all) on every vacation rule and the errormsg on unrecognized multi-branch rules. See the feature documentation for further details.

SCR-1875

Summary: New optional parameter applyDefaultAlarms for the iCal import request

Effective: 8.54.320 and later

The iCalendar import request import?action=ICAL accepts the new optional parameter applyDefaultAlarms. When set to true, alarms contained in the imported iCalendar data are skipped for appointments, and the calendar user's configured default alarms (defaultAlarmDate and defaultAlarmDateTime) are applied instead. It has no effect on tasks.

The appointment imported by the following request ends up with the user's default alarms, although the iCalendar data carries no VALARM component:

POST /ajax/import?action=ICAL&folder=cal%3A%2F%2F0%2F31&applyDefaultAlarms=true
Content-Type: multipart/form-data; boundary=--boundary

--boundary
Content-Disposition: form-data; name="file"; filename="appointment.ics"
Content-Type: text/calendar

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp//Booking//EN
BEGIN:VEVENT
UID:5a1c0f6e-3d21-4a77-9d1f-1e0b7c2a9f44
DTSTAMP:20260901T100000Z
DTSTART:20270301T100000Z
DTEND:20270301T110000Z
SUMMARY:Flight to Berlin
END:VEVENT
END:VCALENDAR
--boundary--

This addresses appointments that do not originate from the user's own calendar, e.g. one added from a mail attachment: such data usually carries either no alarm at all or an alarm chosen by whoever created the file, so the user ends up without the reminders they configured. Since the server cannot tell where the data came from, the client decides. The change is purely additive - without the parameter, alarms are imported as before, which keeps export/import round trips within the calendar module intact.

See the import request documentation for further details.

SCR-1874

Summary: Moved the user field guest_created_by from column 616 to column 627

Effective: 8.54.320 and later

The user module exposed the field guest_created_by as column 616, the very same identifier the contacts module uses for yomiFirstName. The user field has been moved to the previously unused column 627, so column 616 now denotes yomiFirstName in every module.

This is not backward compatible. Clients that request column 616 from user?action=all, user?action=list or user?action=search no longer receive guest_created_by but the contact's yomiFirstName; they have to request column 627 instead. The JSON field name guest_created_by is unchanged, hence user?action=get is not affected.

See the detailed user data for further details.

SCR-1860

Summary: New setting stating the trash folders of the OpenCloud/Nextcloud accounts of a user

Effective: 8.54.320 and later

The trash folder of every OpenCloud or Nextcloud account of the user is stated by the setting modules/files/opencloud/folder/trash or modules/files/nextcloud/folder/trash of the config tree, so io.ox/files//opencloud/folder/trash or io.ox/files//nextcloud/folder/trash as a JSLob path. For OpenCloud that folder is a virtual one holding the trash bins of the spaces, so it cannot be derived from the identifier of an account by a client. For Nextcloud it is the user’s trash bin the instance states.

The value is an object holding one fully qualified folder identifier per account, by the identifier of that account. It is empty for a user without an OpenCloud or Nextcloud account, and the setting is not available at all for a user without the infostore module.

{"1":"opencloud://1/L3RyYXNo"}
{"2":"nextcloud://2/L3JlbW90ZS5waHAvZGF2L3RyYXNoYmluL2FudG9uQGNvbnRleHQxLm94LnRlc3QvdHJhc2gv"}

SCR-1858

Summary: New actions and columns for the shares of items within external file storages

Effective: 8.54.320 and later

The shares an item has within an external file storage that supports being shared that way are stated and managed through the infostore module.

  • PUT /infostore?action=createShare creates a share, GET /infostore?action=listShares lists them, PUT /infostore?action=updateShare updates one, and PUT /infostore?action=removeShare removes them. The parameter id is optional for all of them: stated, it denotes the file to share, omitted, the folder the parameter folder states is shared.
  • The column 7050 of the detailed infoitem data and the column 3250 of the detailed folder data state the shares of an item, each of them as an object holding the properties of one share.

Purely additive — existing requests are unaffected.

See the API documentation for further details.

API - Java

SCR-1880

Summary: Changed behavior of Sieve script parsing: "elsif"/"else" control blocks become branches of a single rule instead of unsupported rules

Effective: 8.54.320 and later

The mail filter middleware now parses Sieve rules containing elsif/else control blocks into a single rule holding all branches; previously every such block was reported as a separate unsupported rule. The change applies to every parsed script — hand-written rules included — regardless of the feature toggle of the accompanying Configuration SCR.

Implementations of the com.openexchange.mailfilter.MailFilterInterceptor Java interface see these rules on every read and write and must not assume single-branch rules any more: a Rule may now carry several branches (IfCommand followed by ElsifCommand/ElseCommand entries), more than one vacation action, and an else branch without a test command. The new Rule.getBranches() method enumerates a rule's branches; Rule.hasMultipleBranches() names the multi-branch check. Both are documented on Rule and MailFilterInterceptor. Deployments without own interceptor implementations are unaffected.

Consequences for scripts containing such blocks:

  • their commands now participate in the Sieve capability validation and contribute to the regenerated require line on every write; a command requiring a capability the Sieve server does not announce makes the write fail (previously such blocks were preserved verbatim and ignored by the validation)

  • a script write-back regenerates the blocks in canonical formatting instead of copying them verbatim — comments and custom formatting inside hand-written elsif/else blocks are lost on the next write through the middleware; note that saving any rule regenerates the whole script, so an edit of an unrelated rule already triggers this

  • rule listings shift: every such block used to occupy a list position of its own as an "unsupported" error rule; it now merges into its preceding rule, so affected scripts list fewer rules and following rules move up (the merged error rules were not editable anyway)

Script parsing additionally normalizes lone CR characters (classic-Mac line breaks) to CRLF, inside string literals such as vacation texts too; a script containing lone CRs — e.g. written through ManageSieve — is persisted in the normalized form on its next write through the middleware.

API - REST

SCR-1986

Summary: New administrative REST endpoints for inspecting the cross-context grant index

Effective: 8.54.320 and later

Two preliminary endpoints under HTTP basic authentication report which foreign contexts hold cross-context access on a resource, as recorded in the xctx_grants index:

  • GET /preliminary/crosscontext/v1/grants/{context}/{entity} - for one resource owner, e.g. a shared account
  • GET /preliminary/crosscontext/v1/grants/{context} - for every resource owner of a context that has one
{"context": 1337, "entity": 14, "grants": {"SHARED_ACCOUNT": [42, 58]}}

The grants are keyed by type rather than by groupware module, because a grant can be module-less. Both endpoints report the index verbatim: no backing store is consulted, there is no fallback when the index holds nothing, and the trust-zone authority is not applied - so an empty result means nothing is recorded, not that nothing exists. Purely additive.

See the REST API documentation for further details.

SCR-1939

Summary: SCIM: query parameter deleteMode on DELETE /Users/{id} for an immediate hard delete

Effective: 8.54.320 and later

The SCIM endpoint accepts the query parameter deleteMode on DELETE /scim/v2/contexts/{cid}/Users/{id} with the values deactivate, softDelete and delete, overriding the mode configured through com.openexchange.scim.deleteMode for that call. With delete the account is removed at once even in a context configured for softDelete, and an account that is soft-deleted already (and therefore hidden from SCIM) is removed as well, bypassing the retention period: the way to honor a request under GDPR Art. 17 without waiting. An unknown value is answered with 400 invalidValue. Without the parameter nothing changes. OpenAPI document and documentation (administration/scim.md, section "Delete mode") updated.

SCR-1925

Summary: Soft-deleting and restoring users over the provisioning API

Effective: 8.54.320 and later

UserService.DeleteUsers takes the enum field mode (HARD, the default, or SOFT), the new RestoreUsers (POST /prov/v1/contexts/{context_id}/users:restore) lifts the soft-deleted state, and ListUserData lists the soft-deleted users of a context with the filter soft_deleted=true. The User message carries the read-only soft_deleted (milliseconds since the epoch), absent for every other user. Soft-deleting the context administrator, a guest or an already soft-deleted user, and restoring a user that is not soft-deleted, yield 400.

DeleteUsers with mode: SOFT now honors dest_user, which it accepted and ignored before: it names the user the shared data is reassigned to once the retention period has passed. Absent leaves it to the context administrator, 0 or less drops the data. A destination that does not exist, is a guest, is soft-deleted itself or is one of the users being soft-deleted yields 400.

SCR-1918

Summary: Added the /SharedAccounts, /Deputies and /SecondaryAccounts types to the SCIM 2.0 service provider

Effective: 8.54.320 and later

The SCIM 2.0 service provider ([SCR-1901], [SCR-1907], [SCR-1914]) now covers the shared accounts of a context: mailboxes and calendars several users work in, provisioned through OXSharedAccountInterface. It also covers deputies, provisioned through OXDeputyPermissionsInterface, and the secondary mail accounts of the users, provisioned through OXSecondaryAccountInterface.

  • New resource type SharedAccount under /scim/v2/contexts/{context_id}/SharedAccounts with the schema urn:ietf:params:scim:schemas:extension:openxchange:2.0:SharedAccount: the profile attributes of a user (userName, displayName, name, emails, preferredLanguage, timezone, phone numbers, addresses, the enterprise extension) without active and groups; password (write only, the mailbox password, random when omitted); aliases (the mailbox address is always part of it); permissions, one entry per user or group of the context with value, $ref, type (User or Group), mail and calendar as permission level none, viewer, editor, author or admin (absent for no access), grantedCapabilities and deniedCapabilities such as sendAs. GET with filter (eq on userName, displayName, emails.value, externalId, combinable with and), startIndex and count; POST, GET, PUT, PATCH, DELETE with the versioning of [SCR-1909]. userName and displayName are unique within the context (409 uniqueness). A listing leaves permissions out, since they cost a provisioning call per account; a single read carries them.
  • A PUT applies what the document carries and keeps the rest: the provisioning API converts a shared account to a user on its way to the storage and skips unset fields, so its attributes cannot be cleared. permissions omitted on PUT stay as they are; an empty list, or a PATCH removing the attribute, removes every permission granted within the context. Permissions are set per distinct access in one call; a permission naming a user or group that does not exist, or an unknown capability, is refused with 400 invalidValue, and on POST the just created account is removed again in that case. Permissions granted to users of other contexts are neither shown nor touched.
  • DELETE removes the account and its mailbox; a shared account cannot log in, so there is nothing to deactivate. externalId is stored with resource type 4 and removed with the account; shared accounts stay invisible under /Users.
  • Discovery: /ResourceTypes lists the SharedAccount, Deputy and SecondaryAccount types, /Schemas their schemas.
  • The photo, the full set of phone and fax types and the OX-specific contact fields of the user extension ([SCR-1908]) apply to the shared account as well.
  • New resource type Deputy under /scim/v2/contexts/{context_id}/Deputies with the schema urn:ietf:params:scim:schemas:extension:openxchange:2.0:Deputy: grantor (the user delegating, required and immutable; a different one is refused with 400 mutability), deputy (value, $ref, type User or Group; immutable as well), sendOnBehalfOf, folderMode (default, all, specific), classifiedAccess (none, confidential, private) and modules, one entry per module with permission as level none, viewer, editor, author or admin and, for specific, the folders. Addressed by the identifier the deputy service assigns; an externalId of the client's own system is stored and searchable. GET with filter (eq on grantor.value, deputy.value, deputy.type, externalId), POST, PUT, PATCH, DELETE; a PUT keeps what it does not carry, and "externalId": null removes the external identifier. A grant made outside the simple permission mode reads as custom and cannot be written back; granting a module to the same deputy twice is refused with 409 uniqueness. The module mail needs an administrative way into the grantor's mailbox - a token Dovecot accepts, the mail master account, or DoveAdm. No notification mail is sent.
  • New resource type SecondaryAccount under /scim/v2/contexts/{context_id}/SecondaryAccounts with the schema urn:ietf:params:scim:schemas:extension:openxchange:2.0:SecondaryAccount: one mail account of one user, addressed as {user}-{account}, which stays the same when the address changes. user (required and immutable), primaryAddress (required, unique among the accounts of its user), name (the address when omitted), personal, replyTo, login (required), password (write only), mail and transport with server, port, protocol, secure, startTls and, for transport, its own login and password; on POST a source (none, primary, localhost) for an end-point the document leaves out; the standard folders, the spamHandler and an externalId of the client's own system. GET with filter (eq on user.value, primaryAddress, externalId), POST, PUT, PATCH, DELETE; a PUT keeps what it does not carry, and "externalId": null removes the external identifier. A second account with the same address is refused with 409 uniqueness. Passwords are never returned.

Purely additive. /SharedAccounts requires com.openexchange.sharedaccount.enabled=true, as the provisioning API does.

SCR-1917

Summary: Completed the App Suite user extension of the SCIM 2.0 service provider: capabilities, clearing by null, driveUserFolderMode, filestoreId

Effective: 8.54.320 and later

The App Suite user extension urn:ietf:params:scim:schemas:extension:openxchange:2.0:User of the SCIM 2.0 service provider (SCR-1901) now covers the remaining per-user settings of the provisioning API.

  • New multi-valued string attribute capabilities: the per-user capability overrides as changecapabilities stores them, a name grants a capability, a leading minus denies one. Rendered on single reads only, never in listings. A list sent with PUT or PATCH becomes the stored set: names it adds are granted or denied, names it no longer carries are dropped so the configuration applies again; an omitted attribute keeps the stored overrides, an explicit null or a PATCH remove drops them all. A name the permission configuration forbids is refused with 400 invalidValue; on POST the just created account is removed again in that case. Both a name and its denial in one list are refused.
  • A null alias list, or a PATCH removing aliases, reduces the aliases to the mailbox and sender addresses. A null for, or a PATCH removing, imapLogin, imapServer, smtpServer, defaultSenderAddress, maxQuota, passwordExpired or accessCombinationName, which the provisioning API cannot clear, is refused with 400 and a message naming the value to send instead; previously it was ignored.
  • The version of a single read (meta.version, ETag) now covers the access combination and the capability overrides, so a conditional GET with If-None-Match notices their change instead of answering 304; If-Match on PUT, PATCH and DELETE is checked against that version. The version of a user in a listing still covers only what the listing shows. accessCombinationName and capabilities are declared returned: default in /Schemas.
  • New attribute driveUserFolderMode (default, normal, none, case-insensitive), the driveUserFolderMode of the provisioning create: honored on POST only, declared immutable and returned: never since the provisioning API does not store it; a PUT or PATCH carrying a value is refused with 400 mutability.
  • New integer attribute filestoreId, declared immutable: on POST the user gets an own file storage there, as with the provisioning create; on reads it is rendered while the user has an own storage. A PUT or PATCH may echo the stored value, any other value, and a null while a storage is set, is refused with 400 mutability, since moving a user's files is a job of the provisioning API.

Provisioning error messages that wrap an internal exception are reduced to the message; a missing file store and a quota the provisioning API may not assign are answered with 400 instead of 500. Users with an own file storage gain filestoreId in their representation, and every single read carries the access combination in its version, so versions change once. Everything else is additive; /Schemas lists the new attributes.

SCR-1914

Summary: Added the /Resources type and group memberships on users to the SCIM 2.0 service provider

Effective: 8.54.320 and later

The SCIM 2.0 service provider (SCR-1901, SCR-1907) now covers the resources of a context and shows memberships from both sides.

  • New resource type Resource under /scim/v2/contexts/{context_id}/Resources with the schema urn:ietf:params:scim:schemas:extension:openxchange:2.0:Resource: displayName and email are required, name is derived from the display name unless sent (lower-cased, blanks turned into dashes, accented letters reduced, a counter appended where taken), description, available (true by default) and permissions, a list of {value, type, privilege} where value is a user or group identifier, type is User or Group and privilege one of none, ask_to_book, book_directly, delegate. GET with filter (eq on name, displayName, email, externalId, combinable with and), startIndex and count; POST, GET, PUT, PATCH, DELETE with the versioning of SCR-1909. name and email are unique within the context (409 uniqueness), a name sent explicitly is checked against CHECK_RES_UID_REGEXP, permissions follow com.openexchange.resource.simplePermissionMode. Permissions omitted on PUT stay as they are; an empty list restores the default. externalId is stored with resource type 3 and removed with the resource.
  • Users carry the read-only groups attribute on single reads (value, $ref, display of every group the user is a member of; declared returned: request), groups carry members[].display on single reads. Listings leave both out, so meta.version agrees between a listing and a single read.
  • Discovery: /ResourceTypes lists the Resource type, /Schemas its schema.

Purely additive. Attributes declared returned: request are rendered only when the attributes parameter of a single read names them.

SCR-1910

Summary: Added JSON Web Tokens as bearer credential to the SCIM 2.0 service provider

Effective: 8.54.320 and later

The SCIM 2.0 service provider (SCR-1901) now accepts a JSON Web Token issued by the identity provider as Bearer credential next to provisioning tokens, once com.openexchange.scim.allowJwt is enabled for the context. A bearer value containing a dot is treated as JWT; provisioning tokens never contain one.

  • The token is validated by the OAuth provider in mode expect_jwt (signature against the configured JWK set, issuer, audience, expiry) and resolved to a context and a user through the provider's claim lookup (contextLookupClaim, userLookupClaim). It has to resolve to the context in the URL, its subject has to be the context administrator, and its scope has to carry scim (mapped through com.openexchange.oauth.provider.scope.[EXTERNAL_SCOPE]). Requests made with it act as the context administrator, like provisioning tokens.
  • A valid token for another user or without the scope is answered with 403; an invalid, expired or foreign token with 401; 503 while the provider cannot reach its key source or no OAuth authorization service is available. Refusals are logged with the client (azp) and the reason, and never counted against the lock for failed basic authentications.
  • /ServiceProviderConfig lists the scheme JSON Web Token once it is enabled.

Purely additive; nothing changes while the property stays at its default.

SCR-1909

Summary: Added resource versions with If-Match and read-only checks to the SCIM 2.0 service provider

Effective: 8.54.320 and later

The SCIM 2.0 service provider (SCR-1901, SCR-1907) now versions its resources (RFC 7644, section 3.14) and refuses PATCH operations on read-only attributes.

  • Every user and group carries meta.version, a weak entity tag derived from the attributes of its representation, and is returned with the ETag header on single reads, POST, PUT and PATCH; /ServiceProviderConfig advertises etag.supported=true. The version is the same on every node and does not depend on the request's host.

  • PUT, PATCH and DELETE /Users/{id} and /Groups/{id} honor If-Match (entity tags or *, weak comparison): a resource that changed since the client read it is answered with 412 and nothing is written. If-None-Match on a single read yields 304. Without these headers the behavior is unchanged, the last writer wins.

  • A PATCH operation whose path targets a read-only attribute (id, meta, groups) is refused with 400 mutability instead of being applied and silently dropped. Read-only attributes inside a path-less value, as echoed back by some providers, are ignored the way PUT ignores them.

Purely additive for clients that do not send the headers.

SCR-1908

Summary: Added the App Suite user extension to the SCIM 2.0 service provider

Effective: 8.54.320 and later

Settings a directory does not model, but an operator wants to drive per user, are now available on /scim/v2/contexts/{context_id}/Users in the extension urn:ietf:params:scim:schemas:extension:openxchange:2.0:User, declared in /Schemas and listed for the User resource type. Unlike the core attributes, an extension attribute the document omits on PUT keeps its value; only what is sent changes.

  • accessCombinationName: the named module access combination from ModuleAccessDefinitions.properties, applied on create and changed through the provisioning API on update. An unknown name is refused with 400. Declared returned: request and returned on single reads only.
  • aliases: the alias set. The mailbox address and the default sender address are always part of it, as the provisioning API insists on; when the mailbox address changes, the old one is dropped.
  • defaultSenderAddress: added to the aliases where missing; a sender equal to the mailbox address follows it when that changes.
  • maxQuota in megabytes (-1 for unlimited), imapLogin, imapServer, smtpServer and passwordExpired.
  • The OX-specific personal and company contact fields that have no home in the core or enterprise schema: birthday and anniversary as ISO dates, maritalStatus, numberOfChildren, spouseName, profession, roomNumber, assistantName, note, info, categories, businessCategory, commercialRegister, taxId, salesVolume, and the 20 free-form fields as the positional array userFields. Like the other extension attributes they are cleared by an explicit null.

For the context administrator, mail servers and password state cannot be changed over SCIM (403), in addition to password, deactivation and deletion: pointing the administrator's mail account at another host would hand that password over. Purely additive.

SCR-1907

Summary: Added the /Groups endpoint to the SCIM 2.0 service provider

Effective: 8.54.320 and later

The SCIM 2.0 service provider (SCR-1901) now serves the groups of a context under /scim/v2/contexts/{context_id}/Groups, so an identity provider can provision group memberships next to the users. Authentication, media types, body limits and error model are those of the users endpoint.

  • GET /Groups with filter (eq on displayName, externalId and the App Suite name, combinable with and), startIndex and count; GET /Groups/{id}. Members are rendered with value, $ref and type.
  • POST /Groups: displayName is required and unique within the context (409 uniqueness otherwise). The identifier the provisioning API needs besides lives in the extension urn:ietf:params:scim:schemas:extension:openxchange:2.0:Group as name; when a client does not send it, it is derived from the display name (lower-cased, blanks turned into dashes, accented letters reduced to their base letter, a counter appended where the name is taken). A name sent explicitly is checked against CHECK_GROUP_UID_REGEXP.
  • PUT /Groups/{id} replaces display name and members; a document without members empties the group. PATCH /Groups/{id} applies add with a list of members, remove with members[value eq "..."] or with the members named in value, and replace of displayName; adding a member twice is harmless. DELETE /Groups/{id} removes the group.
  • Only regular users can be members; a member referring to a guest or an unknown user is refused with 400 invalidValue. The context's standard group is readable but refuses changes and deletion with 403.
  • externalId is stored in scim_external_id with resource type 2 and searchable. /ResourceTypes lists the Group type and /Schemas the core group schema and the extension.

Purely additive. See the administration article "SCIM provisioning" for the mapping and the identity provider setup.

SCR-1901

Summary: Added a SCIM 2.0 service provider for provisioning the users of a context from an identity provider

Effective: 8.54.320 and later

In order to let an identity provider such as Entra ID, Okta, authentik or the Univention Nubus provisioning client drive the users of a context with the SCIM connector it already ships, the middleware now acts as a SCIM 2.0 service provider (RFC 7643, RFC 7644). The context is the SCIM tenant and sits in the base URL /scim/v2/contexts/{context_id}; the endpoint is served by the new package open-xchange-scim on the admin role and is meant to be routed to a public hostname for exactly the /scim/ prefix.

  • GET /ServiceProviderConfig, GET /ResourceTypes and GET /Schemas: discovery, declaring the supported subset of the core user schema and the enterprise extension.
  • GET /Users with filter (eq on userName, externalId, emails.value and displayName, combinable with and), startIndex and count (at most 200); other filters are refused with invalidFilter.
  • POST /Users, GET, PUT, PATCH and DELETE /Users/{id}: PATCH follows RFC 7644 including paths such as emails[type eq "work"].value and the string booleans Entra ID sends for active; attribute names are matched case-insensitively and unknown ones are refused with invalidPath. externalId is stored and searchable. active maps to mailenabled. A user created without password gets a random, unusable one and logs in through single sign-on.
  • DELETE deactivates by default; see SCR-1902 for com.openexchange.scim.deleteMode.
  • The mapped core attributes cover the full contact record: photos carried as a base64 data: URI, and phoneNumbers for every phone and fax slot the user has (work, work2, home, home2, mobile, mobile2, fax, fax_home, fax_other, pager, other, assistant, callback, car, company, ip, isdn, primary, radio, telex, ttytdd), alongside the name, e-mail, instant-messaging and postal-address attributes and the enterprise extension.

Callers authenticate with a Bearer provisioning token of scope SCIM bound to the context (SCR-1897), which acts as the context administrator, or with HTTP basic credentials checked like every provisioning operation. The endpoint never grants access without a credential, refuses basic credentials while administrator authentication is disabled in the deployment, and locks a login for fifteen minutes after ten failed basic attempts, answered with 429. Only regular accounts are visible; the context administrator cannot be deactivated, deleted or given a password over SCIM. Request bodies are limited to one megabyte. Responses use application/scim+json; application/json is accepted in Accept and Content-Type. Purely additive. See the administration article "SCIM provisioning" for the attribute mapping and the identity provider setup.

SCR-1897

Summary: Added provisioning tokens as bearer credentials for scoped provisioning interfaces

Effective: 8.54.320 and later

In order to let automated clients such as an identity provider or a provisioning script authenticate without an administrator's password, the provisioning API gains provisioning tokens: bearer secrets bound to one scope and to one context or, as cross-context tokens, to several contexts at once, managed by the new gRPC service ProvisioningTokenService, which the HTTP gateway exposes by default.

  • POST /prov/v1/contexts/{context_id}/tokens creates a token from label, scope (SCIM or PROVISIONING) and an optional expires in milliseconds since the epoch (0 or absent: the token does not expire). The response carries the metadata and, exactly once, the secret of the form ox_<context-id>_<64 hex characters>.
  • GET /prov/v1/contexts/{context_id}/tokens lists the tokens of a context without secrets, including lastUsed and, in contextIds, the contexts a token opens.
  • DELETE /prov/v1/contexts/{context_id}/tokens/{token_id} revokes a token.
  • POST /prov/v1/tokens creates a cross-context token from contextIds (at least two distinct contexts), label, scope and expires; its secret has the form ox_x_<64 hex characters>. GET /prov/v1/tokens lists the cross-context tokens the caller stands above, DELETE /prov/v1/tokens/{token_id} revokes one.
  • GET /prov/v1/contexts/{context_id}/cross-context-tokens lists the cross-context tokens that open a context, for the administrator of that context, each naming that context and no other; DELETE /prov/v1/contexts/{context_id}/cross-context-tokens/{token_id} ends that one context's exposure without touching the others.

The tokens of a context are managed like every other operation inside a context: by the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator. Cross-context tokens are managed only by the master administrator or a reseller administrator owning every one of the contexts; creating one additionally needs MASTER_ACCOUNT_OVERRIDE: with the override off no administrator reaches into a context, so none may issue a token that acts inside one, while listing and revoking work regardless, so what was issued before can still be seen and taken back. A cross-context token is issued above the contexts, so the context it opens has no part in it; its administrator can however see which cross-context tokens reach into the context and detach the context from one, which ends that exposure without revoking the token and is no lasting veto. They are stored in the configuration database, and a context that is deleted is dropped from them; a token left without a context is deleted with it. Only the SHA-256 hash of a secret is stored.

A token of scope SCIM opens the SCIM service provider of each context it is bound to. A token of scope PROVISIONING is presented on /prov as Authorization: Bearer <secret> and acts as an administrator of exactly the contexts it is bound to: any other context, every master operation such as creating a context, and the token services themselves are refused with 401. A cross-context token of that scope is what lets one credential grant shared account permissions across contexts.

The new bundles com.openexchange.provisioning.token and com.openexchange.provisioning.token.impl ship with open-xchange-core; the service com.openexchange.grpc.provisioning.ProvisioningTokenService is part of the gateway's default provisioningGateway.services list. Purely additive, existing operations are unaffected.

SCR-1861

Summary: New REST end-point POST /authentication/v1/resolve to resolve a user to its identifiers

Effective: 8.54.320 and later

In order to let an external authentication service look up a user before a session exists, the new end-point POST /authentication/v1/resolve resolves a user to its user identifier, context identifier and the database schema its context lives in. It is protected by Basic authentication like the other end-points of that bundle.

The request body denotes the user in exactly one of two shapes:

  • loginInfo with contextName and userName - the caller has already mapped its identity provider's response onto the login names, which are looked up as-is.

  • identity with type and value - an opaque identifier the caller cannot map itself, left to a deployment-specific resolver.

    A body that sets both or neither is answered with 400, a user no resolver knows with 404.

    The resolution itself is provided by implementations of the new OSGi service com.openexchange.external.authentication.common.api.identity.UserResolver, exported by bundle com.openexchange.external.authentication.common. They are consulted in the order of their service ranking; the built-in resolver has the lowest possible ranking and handles the loginInfo shape only, so a deployment can contribute its own resolver for opaque identifiers without displacing it.

API - RMI

SCR-1943

Summary: New RMI interface OXProvisioningTokenInterface

Effective: 8.54.320 and later

The new RMI interface com.openexchange.admin.rmi.OXProvisioningTokenInterface, bound as OXProvisioningToken, manages provisioning tokens, as the gRPC service ProvisioningTokenService does. For the tokens of a context, create(Context, String label, String scope, long expires, Credentials) returns the new token together with its secret, list(Context, Credentials) the tokens of the context without secrets, and revoke(Context, String tokenId, Credentials) whether a token was revoked. For cross-context tokens, which open several contexts at once, createCrossContext(Context[] ctxs, String label, String scope, long expires, Credentials) takes at least two distinct contexts and the credentials of the master administrator or a reseller administrator owning every one of the contexts, and only where MASTER_ACCOUNT_OVERRIDE lets an administrator reach into a context at all, listCrossContext(Credentials) returns the cross-context tokens the caller stands above, which needs no override for the master administrator, and revokeCrossContext(String tokenId, Credentials) whether one was revoked; a context administrator is refused on all three. The context reached into is not left without a say: listCrossContextReachingInto(Context, Credentials) returns the cross-context tokens that open the given context, each naming that context and no other, and detachCrossContext(Context, String tokenId, Credentials) ends that one context's exposure - the token keeps working for every other context it opens and is deleted only if this was the last one. Both are authenticated against the context reached into, so its own administrator may call them; neither is a revocation nor a lasting veto. Tokens travel as the serializable data object com.openexchange.admin.rmi.dataobjects.ProvisioningToken, whose secret is set only on the result of a create; getContextIds() names the contexts a token opens, and a cross-context token has getContextId() 0 and isCrossContext() true. The context's schema is brought up to date first, and invalid input such as an unknown scope, an expiration time in the past or a single context for a cross-context token is refused with InvalidDataException. The interface is registered by com.openexchange.admin and is not site-aware. Purely additive.

SCR-1923

Summary: Soft-deleting and restoring users over OXUserInterface

Effective: 8.54.320 and later

OXUserInterface offers softDelete(Context, User[], Credentials), restore(Context, User[], Credentials), both also for a single user, and listSoftDeleted(Context, Credentials). A soft-deleted user is a leaver: hidden from the address book, not invitable, unable to log in, deleted for good after the retention period, while the data is kept and the login name stays reserved (see the behavioral change). The context administrator, guests, shared accounts and users that are already soft-deleted are rejected with InvalidDataException, as is restoring a user that is not soft-deleted. The data object User carries the read-only softDeleted (java.util.Date), null for every other user; the listing returns identifier and softDeleted only.

softDelete is also offered as softDelete(Context, User[], Integer, Credentials) and for a single user, where the Integer names the user the shared data is reassigned to once the retention period has passed, with the same meaning as destUser on delete: null leaves it to the context administrator, 0 or less drops the data. The destination is checked when it is named - it must exist and may be neither a guest, nor the user being soft-deleted, nor a soft-deleted user itself (InvalidDataException) - and checked again before it is used. restore forgets it.

SCR-1889

Summary: New Field classifiedAccess in the Deputy Permission Data Objects of the Administrative RMI and gRPC Interfaces

Effective: 8.54.320 and later

The data objects com.openexchange.admin.rmi.dataobjects.DeputyPermission (and thereby ActiveDeputyPermission) and DeputyPermissionDescription used by OXDeputyPermissionsInterface gained the field classifiedAccess next to sendOnBehalfOf, with the accessors getClassifiedAccess() and setClassifiedAccess(String) (plus isClassifiedAccessSet() / removeClassifiedAccess() on the description). It carries the level of the granting user's classified appointments a deputy may see with all details in the covered calendar folders: none or confidential, null when not granted; appointments marked as private always stay hidden from deputies, and the attribute has no meaning for other modules. The gRPC provisioning interface exposes the same as field classified_access (number 7) of the DeputyPermission message and as classifiedAccess / ClassifiedAccessSet (numbers 13 and 14) of the DeputyPermissionDescription message in deputy.proto. A value other than none or confidential is rejected with an InvalidDataException; null still means none when granting, and an attribute not set on the description leaves the level unchanged when updating.

Purely additive - the serialVersionUIDs of the data objects are unchanged, so RMI clients built against the previous version keep working, and the proto fields are wire compatible. See the feature documentation for further details.

API - SOAP

SCR-1950

Summary: New SOAP service OXProvisioningTokenService

Effective: 8.54.320 and later

The new SOAP service OXProvisioningTokenService manages provisioning tokens, the bearer secrets an automated client such as an identity provider driving the SCIM service provider or a script driving the provisioning API authenticates with, and offers what the command line tools createprovisioningtoken, listprovisioningtokens and revokeprovisioningtoken do. create takes the context, a label of at most 128 characters, the scope (scim or provisioning) and an optional expires in milliseconds since the epoch (0 or absent: no expiry) and returns the token with its secret, the only time the secret is shown; list returns the tokens of the context with their lastUsed time but without secrets; revoke takes the tokenId and returns true if the token existed. For cross-context tokens, which open several contexts at once, createCrossContext takes the contexts as ctxs (at least two distinct ones) with label, scope and expires, listCrossContext returns the cross-context tokens the caller stands above, and revokeCrossContext takes the tokenId; creating needs the master administrator or a reseller administrator owning every one of the contexts, and only where MASTER_ACCOUNT_OVERRIDE lets an administrator reach into a context at all, listing and revoking the master administrator or, where MASTER_ACCOUNT_OVERRIDE permits it, a reseller administrator owning every one of the contexts. For the context reached into, listCrossContextReachingInto takes the context and returns the cross-context tokens that open it, each naming that context and no other, and detachCrossContext takes the context and a tokenId and ends that one context's exposure; both are authenticated against that context, so its own administrator may call them. The ProvisioningToken element carries the contexts a token opens in contextIds; a cross-context token has contextId 0. Authorization for a context follows every other operation inside a context: the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator. The service ships with open-xchange-admin-soap; its WSDL is at /webservices/OXProvisioningTokenService?wsdl. Example request and response, and a cross-context request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.admin.openexchange.com" xmlns:xsd="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:xsd1="http://dataobjects.rmi.admin.openexchange.com/xsd">
  <soapenv:Header/>
  <soapenv:Body>
    <soap:create>
      <soap:ctx>
        <xsd:id>1</xsd:id>
      </soap:ctx>
      <soap:label>Entra ID</soap:label>
      <soap:scope>scim</soap:scope>
      <soap:auth>
        <xsd1:login>oxadmin</xsd1:login>
        <xsd1:password>secret</xsd1:password>
      </soap:auth>
    </soap:create>
  </soapenv:Body>
</soapenv:Envelope>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:createResponse xmlns="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:ns2="http://soap.admin.openexchange.com">
      <ns2:return>
        <id>5b0e7c2a9d4f4e1b8c3a6d9f2e1b4c7a</id>
        <contextId>1</contextId>
        <contextIds>1</contextIds>
        <label>Entra ID</label>
        <scope>scim</scope>
        <createdBy>oxadmin</createdBy>
        <created>1789112149008</created>
        <expires>0</expires>
        <lastUsed>0</lastUsed>
        <secret>ox_1_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</secret>
      </ns2:return>
    </ns2:createResponse>
  </soap:Body>
</soap:Envelope>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.admin.openexchange.com" xmlns:xsd="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:xsd1="http://dataobjects.rmi.admin.openexchange.com/xsd">
  <soapenv:Header/>
  <soapenv:Body>
    <soap:createCrossContext>
      <soap:ctxs>
        <xsd:id>1</xsd:id>
      </soap:ctxs>
      <soap:ctxs>
        <xsd:id>2</xsd:id>
      </soap:ctxs>
      <soap:label>Shared account automation</soap:label>
      <soap:scope>provisioning</soap:scope>
      <soap:auth>
        <xsd1:login>oxadminmaster</xsd1:login>
        <xsd1:password>secret</xsd1:password>
      </soap:auth>
    </soap:createCrossContext>
  </soapenv:Body>
</soapenv:Envelope>

SCR-1924

Summary: Soft-deleting and restoring users over the OXUserService SOAP interface

Effective: 8.54.320 and later

The OXUserService offers the operations softDelete, softDeleteMultiple, restore, restoreMultiple and listSoftDeleted, the SOAP counterparts of the new OXUserInterface methods. The User element carries the read-only soft_deleted (xs:dateTime), absent for a user that is not soft-deleted. Clients that generate their stubs from the WSDL regenerate them to use the operations; existing operations are unchanged.

softDelete and softDeleteMultiple take the optional element reassign, the user the shared data is reassigned to once the retention period has passed, with the same meaning as on delete.

SCR-1921

Summary: Administrative SOAP operations accept a plain contextId, and moveContextFilestore reports the correct fault

Effective: 8.54.320 and later

Several administrative SOAP operations now accept the context identifier directly, as an alternative to sending a whole ctx element. The new contextId element is optional and appended after the existing ones, so requests that do not use it are unaffected. When both are given, contextId wins.

Affected operations:

  • OXTaskMgmtService: flush, deleteJob, getJobList, getTaskResults
  • OXGroupService: listAll

A request may now be sent as:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:s="http://soap.admin.openexchange.com"
                  xmlns:r="http://dataobjects.rmi.admin.openexchange.com/xsd">
  <soapenv:Body>
    <s:listAll>
      <s:auth>
        <r:login>oxadmin</r:login>
        <r:password>secret</r:password>
      </s:auth>
      <s:contextId>1</s:contextId>
    </s:listAll>
  </soapenv:Body>
</soapenv:Envelope>

Existing clients are unaffected on the wire: the element is minOccurs="0" and the previous element order is unchanged. Clients that compile against the generated Java stubs of getJobList, getTaskResults or listAll see one additional parameter after regenerating them.

Independently of that, OXContextService.moveContextFilestore reported a NoSuchReasonException fault when the destination filestore did not exist, although the operation declares NoSuchFilestoreException for exactly that case. It now reports the declared fault. The WSDL is unchanged; only which of the two declared faults is returned differs.

SCR-1888

Summary: New Element classifiedAccess in the Deputy Permissions of the OXDeputyPermissionsService

Effective: 8.54.320 and later

The DeputyPermission and ActiveDeputyPermission types of the OXDeputyPermissionsService SOAP interface gained the optional element classifiedAccess, next to sendOnBehalfOf. It lets the granting user allow the deputy to see appointments marked as confidential in the covered calendar folders with all details, independently of the deputy attending them; like sendOnBehalfOf it is an attribute of the whole deputy permission, but it exclusively concerns the calendar. Accepted values are none (default) and confidential (confidential appointments are shown with details); appointments marked as private always stay hidden from deputies. The element is honored by grant and update (where it is only applied when present, so none revokes the right), and returned by list and getDeputyPermission; it is absent when the right was not granted. A level that cannot be granted fails with DEPUTY-0014. A value other than none or confidential is rejected as invalid data; an absent value still means none when granting and leaves the level unchanged when updating.

Example grant request appointing user 7 as calendar deputy of user 4 with access to confidential appointments:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.admin.openexchange.com" xmlns:xsd="http://dataobjects.soap.admin.openexchange.com/xsd" xmlns:xsd1="http://dataobjects.rmi.admin.openexchange.com/xsd">
  <soapenv:Header/>
  <soapenv:Body>
    <soap:grant>
      <soap:deputyPermission>
        <xsd:userId>7</xsd:userId>
        <xsd:sendOnBehalfOf>false</xsd:sendOnBehalfOf>
        <xsd:classifiedAccess>confidential</xsd:classifiedAccess>
        <xsd:modulePermissions>
          <xsd:moduleId>calendar</xsd:moduleId>
          <xsd:folderPermission>2</xsd:folderPermission>
          <xsd:readPermission>4</xsd:readPermission>
          <xsd:writePermission>0</xsd:writePermission>
          <xsd:deletePermission>0</xsd:deletePermission>
          <xsd:admin>false</xsd:admin>
        </xsd:modulePermissions>
      </soap:deputyPermission>
      <soap:context><xsd:id>1</xsd:id></soap:context>
      <soap:user><xsd:id>4</xsd:id></soap:user>
      <soap:auth><xsd1:login>oxadmin</xsd1:login><xsd1:password>secret</xsd1:password></soap:auth>
    </soap:grant>
  </soapenv:Body>
</soapenv:Envelope>

A list response carries the element next to sendOnBehalfOf the same way:

<ActiveDeputyPermission>
  <userId>7</userId>
  <sendOnBehalfOf>false</sendOnBehalfOf>
  <classifiedAccess>confidential</classifiedAccess>
  <modulePermissions>
    <moduleId>calendar</moduleId>
    <folderIds>cal://0/31</folderIds>
    ...
  </modulePermissions>
  <deputyId>...</deputyId>
  <grantorId>4</grantorId>
</ActiveDeputyPermission>

Purely additive - a request without the element grants a permission exactly as before, and clients generated from the previous WSDL keep working. See the feature documentation for further details.

Behavioral Changes

SCR-2006

Summary: Reject Own-Object Rights in Cross-Context Folder Permissions

Effective: 8.54.320 and later

A cross-context grant on a calendar or contacts folder may no longer carry an own-objects level for reading, writing or deleting. Creating or updating a folder permission like that is refused with FLD-1039 (INVALID_PERMISSIONS).

A principal from another context never owns objects in the folder's context, so such a level granted nothing at object level. The grantee saw an empty folder, or could create appointments they could then neither change nor delete. All-objects levels, and own-objects levels for users of the folder's own context, are unaffected. The rule applies to cross-context deputy permissions as well.

Grants stored before this change remain in place, and a folder carrying one stays editable: only new or changed entries are checked. No operator action is required.

See the feature documentation for further details.

SCR-2001

Summary: Stricter anti-virus verdicts, more scanned downloads and a scan log

Effective: 8.54.320 and later

Anti-virus scanning fails closed in more cases: an ICAP answer carrying X-Infection-Found, X-Virus-ID or X-Violations-Found counts as infected, and an answer that is neither a finding nor an unmodified response, e.g. a redirect, counts as not scanned instead of clean. Whole mails (.eml, zip_messages, attach_src, get_structure) and zip downloads of PIM and appointment attachments are now scanned as well, and so are files downloaded via share links, which were not scanned before. An encrypted item downloaded via the drive module without decryption is logged as not scanned instead of being reported as clean. Users for whom scanning is enforced get the capability antivirus_enforced. Every scan outcome is logged by com.openexchange.antivirus.scan (findings and refused items at WARN) and counted in appsuite.antivirus.scans.outcome. No admin action; route that logger to your monitoring to review findings.

SCR-1985

Summary: Changed behavior of listing the permission holders of a shared account

Effective: 8.54.320 and later

listSharedAccountPermissionsForSharedAccount (SOAP and RMI, and the listsharedaccountpermissionsbysharedaccount command-line tool) now reports the permissions held by users and groups of every context, not only those that happen to reside in the same database schema as the shared account.

A cross-context permission is stored in the context it was granted to, so querying the account's own schema was always a partial answer whose completeness depended on how contexts were distributed across schemas. The contexts to read are now taken from the xctx_grants index.

No client change is required. On a system upgraded from an earlier version the index is backfilled by a daily job that skips schemas with a pending update task, so until those updates have run the previous, co-located-only result may still be returned.

SCR-1984

Summary: A Nextcloud account whose credentials the instance rejects states an error

Effective: 8.54.320 and later

An account whose credentials a Nextcloud instance rejects is put into an error state rather than having every request of it fail against the instance anew. The rejection is stored with the account, its folders state it towards the client, and it is logged once rather than per request. Throughout the period com.openexchange.file.storage.nextcloud.retryAfterErrorInterval states, see https://jira.atlassian.open-xchange.com/browse/SCR-1971 , the instance is not asked again; afterwards, the next request asks it once more and the error is forgotten if the credentials are accepted. Reconfiguring the account, e.g. by storing a new password or by linking another OAuth account, drops the error immediately, so a corrected account is usable without waiting. Only rejected credentials are remembered this way, any other failure being one of a single request.

SCR-1983

Summary: The Nextcloud file storage supports OAuth, versions, trash, sharing and search

Effective: 8.54.320 and later

The file storage service nextcloud was limited to reading and writing the files of an instance. It supports the following from now on, as far as the instance states that it does:

  • An account may be linked to an OAuth account rather than storing a username and an app token, see https://jira.atlassian.open-xchange.com/browse/SCR-1981 .

  • The versions of a file are listed, read, restored and removed.

  • A deleted item is listed within the trash bin of the account, restored to the location it was deleted from, and purged. It is restored by the instance, so it keeps its versions, and the location it was deleted from is fallen back to the folder a request states if it no longer exists.

  • An item is shared through a link, and the shares granted to the users and the groups of the instance are stated as the permissions of that item and applied from the ones a client states, see SCR-1971. The shared items are listed within virtual folders, see https://jira.atlassian.open-xchange.com/browse/SCR-1982

  • .

  • A search is performed by the instance, which evaluates the name and the media type of an item, while a criterion it does not evaluate is applied by the middleware to the items it states.

  • An item states the link that opens it within the web interface of the instance, and a preview of it is served as its thumbnail, which the instance serves under an endpoint of its own.

See the feature documentation for further details.

SCR-1973

Summary: Custom trust store is now honoured alongside the JVM's default trust store

Effective: 8.54.320 and later

A certificate that only the custom trust store knew was rejected whenever the JVM's default trust store was enabled as well, which is the default. Both stores were served by one trust manager each, and SSLContext only ever uses the first trust manager it is given, so the custom one was never asked. Setting com.openexchange.net.ssl.custom.truststore.enabled to true therefore had no effect unless com.openexchange.net.ssl.default.truststore.enabled was set to false, which in turn dropped every publicly known certificate authority.

Both trust stores are now served by a single trust manager that accepts a certificate as soon as either of them validates it. A certificate issued by an internal certificate authority is trusted while the public ones keep working.

This affects every outbound TLS connection of the middleware that runs with com.openexchange.net.ssl.trustlevel set to restricted; with the default all no certificate is validated in the first place and nothing changes. No configuration change is required. Deployments that worked around the defect by disabling the default trust store can enable it again.

SCR-1972

Summary: An over-sized MCP request body is refused with a JSON-RPC error instead of an empty 413

Effective: 8.54.320 and later

A request body above the MCP endpoint's limit of 1 MiB is answered with a JSON-RPC error object carrying -32700 and a message naming the limit, instead of a bare 413 with no content. A client that speaks only the protocol now learns why it was refused.

The answer is the same whichever way the size becomes known. A body whose Content-Length already exceeds the limit is refused before anything is read; one that never announced its length is refused while it is read. Until now only the second case could answer at all, and it did so differently.

Behind it, the body is parsed straight from a bounded reader rather than collected into a string first, so a request no longer holds its own body twice in memory while it is parsed. No admin action, no configuration.

SCR-1970

Summary: New folder type for the virtual folders of a file storage

Effective: 8.54.320 and later

A file storage may state folders that have no counterpart within it, but list the items of other folders under a certain aspect, such as the ones a user shares. Those folders are meant to be navigated to by a client, while a traversal of the folder tree is supposed to pass them over.

FileStorageFolderType states a value VIRTUAL_FOLDER for them now, which a storage sets on such a folder, and the ZIP archive of a folder passes them over along with the trash folder of the storage. The value is added to an existing enumeration. No storage stated folders of that kind before, so the archives of the existing storages are unchanged except for the Nextcloud one, whose virtual folders are passed over from now on rather than being archived along with the folders their items are located in.

SCR-1965

Summary: Drive offers the OAuth scope read_files

Effective: 8.54.320 and later

The Drive module now offers the OAuth scope read_files, so a user may grant an application read access to their files alone. Its read actions have carried @RestrictedAction(module = "files", type = READ) all along, but no bundle registered the scope with the OAuth provider, so it appeared on no consent screen and could not be granted - the module was out of reach for every token and every OAuth client. It is registered now the way every other module registers its scopes, by com.openexchange.file.storage.json, and requires the capability infostore. Like every module scope it opens the module, not a single feature: a token carrying read_files reads Drive through the HTTP API as well as through the read-only tools of the MCP endpoint (SCR-1945), which is what makes those tools usable in the first place. There is no write_files: writing to Drive stays outside what a scope can grant. No admin action.

SCR-1964

Summary: Cross-Context Shared Account Permissions Are Subject to the Cross-Context Trust Zones

Effective: 8.54.320 and later

A shared account permission for users or groups of another context is now subject to the cross-context trust zones: the shared account and the target context have to share at least one tag of com.openexchange.crosscontext.trustZones, otherwise the grant is refused with SAC-0008, reported as 403 over the HTTP provisioning gateway. There is no separate switch.

A caller that holds authority over the target context anyway is exempt. The policy is skipped whenever the credential in effect for that context - the one supplied for the entry if there is one, the credential of the call otherwise - authenticates against it. That covers an administrator credential valid in both contexts, as well as a scoped provisioning token that covers the target context.

The policy is evaluated when access is newly granted, and only then. Existing permissions keep working whatever the zones say afterwards, revoking is never restricted, updating an existing grant is not affected, and re-sending an unchanged permission set grants nothing new and is therefore not refused.

Nothing that worked before is refused. Granting across contexts previously required credentials for both of them, so every existing caller is covered by the exemption, and the zones only govern the single-credential grant that SCR-1963 newly permits. A deployment without an installed cross-context authority provider is unaffected.

See the general documentation for further details.

SCR-1963

Summary: Shared Account Permissions Across Contexts Require Only the Shared Account's Context Administrator

Effective: 8.54.320 and later

Provisioning a shared account permission for users or groups of another context no longer requires administrator credentials for both contexts. A grant is authenticated against the context of the granting entity, a revocation is accepted from either side - the rule deputy permissions already follow.

  • createSharedAccountPermissions: the shared account's context.
  • deleteSharedAccountPermissions: the shared account's context or the entities' context, whichever accepts the credentials.
  • setSharedAccountPermissions, getSharedAccountPermissions: the shared account's context. The per-context auth sections stay accepted and are still verified when supplied, but are no longer required.

Each call accepts strictly more than before, so no client has to change. The operations also become usable over interfaces that carry a single credential, in particular the HTTP provisioning gateway and SCIM.

See the general documentation for further details.

SCR-1961

Summary: Permanent push keeps its credential in the mail credential vault, persisted regardless of com.openexchange.push.credstorage.rdb

Effective: 8.54.320 and later

Permanent push keeps the credential it needs to run without a session in the mail credential vault (table mail_credential, encrypted with com.openexchange.sessiond.encryptionKey), the way snoozed mail, scheduled mail and the GDPR mail export keep theirs. It does so wherever com.openexchange.push.credstorage.enabled is set to true - persisted in the database, whatever com.openexchange.push.credstorage.rdb says.

Before, com.openexchange.push.credstorage.rdb decided whether the credential was kept in cluster memory (the default) or persisted to the database. It no longer decides that. It only decides where rows of the former storage still live until they are gone: with true, in the credentials table, from where a clean-up job moves them into the vault and deletes them; with false, in cluster memory, from where they are never migrated - they go with the next login of the user, which captures the credential into the vault.

A deployment that enabled the credential storage in order to keep credentials out of the database has to be aware of this: from this version on, the only way to keep them out is to leave com.openexchange.push.credstorage.enabled switched off, at the price of no permanent push for users without a session.

Along with it, deputy permissions managed without DoveAdm report failures of the mailbox access with the codes of the administrative mailbox service (MBADM-0003, MBADM-0004) instead of the former IMAP_DEPUTY- codes for DoveAdm failures, which are gone.

SCR-1945

Summary: New MCP server endpoint for read-only access to mail, calendar, contacts, files and tasks

Effective: 8.54.320 and later

The middleware offers a Model Context Protocol server at /mcp (protocol revision 2026-07-28 and, over the same endpoint, the initialize handshake of revisions 2025-03-26, 2025-06-18 and 2025-11-25, which clients such as claude.ai connectors speak: no session identifier is issued, ping is answered, an unknown method is a JSON-RPC error on 200, and the mirroring headers are optional there, stateless HTTP POST) with read-only tools, prompts and resources. The tools are me_get, mail_search, mail_get, mail_attachment_get, calendar_list_events, calendar_get_event, calendar_free_busy, resources_search, contacts_search, contact_get, users_search, files_search, file_get, tasks_search, task_get, vacation_get, reminders_list and a folder tool per module (mail_folders, calendar_folders, contacts_folders, files_folders, tasks_folders); every list result can be paged through an opaque cursor argument and the nextCursor it returns. The prompts daily_briefing, inbox_triage, find_meeting_slot and catch_up_on chain those tools and are offered only where every tool they drive is available to the caller. Resources are read by URI through the templates ox:///files/{id}, ox:///mail/{folder}/{id} and ox:///mail/{folder}/{id}/attachment/{attachment}, as text or, for anything without a text form, as the first 5 MiB of its bytes; resources/list enumerates nothing. Callers authenticate with a bearer token that the OAuth provider validates, either a JWT from the configured authorization server or a personal access token; tools and resources are visible and usable only with the scopes read_mail, read_calendar, read_contacts, read_files, read_tasks and read_reminders, while me_get is open to every valid token. /.well-known/oauth-protected-resource/mcp serves the RFC 9728 resource metadata. Tool calls and resource reads are rate-limited per user, bounded in how many of one user run at once on a node, and written as one audit line each at level INFO through the logger com.openexchange.mcp.protocol.McpRequestHandler, without argument values or content, and measured through the meters appsuite.mcp.requests, appsuite.mcp.tool.calls and appsuite.mcp.authentications; the core-mw Grafana dashboard gained an MCP row. A bundle that still waits for a needed service, typically the OAuth provider, logs a warning naming it a minute after start. An OpenAPI document describing both paths, the transport headers, every method, the tools, prompts and resources is published as mcp.openapi.json under components/middleware/mcp/<version>/ next to the SCIM and provisioning documents. The endpoint is off by default and needs the chart feature mcp, com.openexchange.mcp.enabled=true and an enabled OAuth provider (com.openexchange.oauth.provider.enabled). The mail tools reach the primary mail account only; secondary and external accounts, which keep their own credentials, are outside a token session's reach. Documentation: documentation/administration/mcp_server.md.

SCR-1938

Summary: Alerting for a failed retention purge of a soft-deleted user

Effective: 8.54.320 and later

The retention job com.openexchange.admin.softdelete.SoftDeleteRetentionExecution counts every purge attempt in the Micrometer counter appsuite.provisioning.softdelete.purges with the tag result=success|failure (Prometheus: appsuite_provisioning_softdelete_purges_total), so an alert can be raised on failures. A failed purge is recorded at the user as the attribute purgeFailed in the namespace softDelete, holding the time and the error, readable over every provisioning API (userAttributes over RMI, user_attributes over gRPC) until the purge succeeds or the user is restored; the restore drops the attribute. The failure is written to the audit trail com.openexchange.provisioning.userLifecycleAuditTrail as well. Before this change a failed purge was only logged at ERROR and retried silently with the next run. Documentation: administration/user_soft_delete.md, administration/cleanup_jobs.md.

SCR-1937

Summary: Audit trail of the user lifecycle: soft-delete, restore, delete and failed purge

Effective: 8.54.320 and later

The provisioning layer writes one line per user and event to the logger com.openexchange.provisioning.userLifecycleAuditTrail at INFO: the soft-deletion, the restore, the deletion for good (by an administrator or by the retention job, then naming the time the user had been soft-deleted), and a failed retention purge. Each line names the acting administrator and, where the request came in over SCIM, the origin the SCIM endpoint records (client, scheme, context). Lines look like:

soft-deleted user 7 in context 1 by oxadmin via scim basic oxadmin in context 1
deleted user 7 in context 1 by soft-delete-retention, soft-deleted since 2026-09-09T11:00:00Z
failed to purge user 7 in context 1 by soft-delete-retention, soft-deleted since 2026-09-09T11:00:00Z: <error>

The logger is meant to be routed to an appender of its own (append-only where the trail is to be kept) with a pattern carrying %lmdc, so the line keeps the tracking identifier and the client address; the appender's timestamp is the time of the event. Without a dedicated appender the lines go to the ordinary log. Documentation: administration/user_soft_delete.md, section "Audit trail".

SCR-1935

Summary: Shares of a soft-deleted user are frozen for every other user

Effective: 8.54.320 and later

While a user is soft-deleted, everything the user has shared is withheld from every other user, without exception: the shared calendars, address books and task folders, the personal files and the files shared one by one, and the shared mail folders (a deputy's access included) are hidden from every other user and cannot be opened, no matter what the stored permissions say. The stored permissions are not touched; the freeze is applied when the effective permission is calculated (folder storage, legacy folder permission, infostore object permissions, the "Shared files" listing, the mail folder tree and the opening of a shared IMAP folder). A restore brings the shares back as they were.

Public folders the user created are no shares and stay accessible. The owner keeps access to the own data, so the data export and the final deletion are unaffected. Before this change, internal shares of a soft-deleted user stayed accessible to the colleagues.

A deputy of a soft-deleted user can neither read the mailbox nor send mail in that user's name: the "send on behalf of" check answers as if the privilege were withheld (MSG-0129), and the grants of a soft-deleted user are absent from the deputy's reverse listings (deputy?action=reverse, reverseIds) until the restore. The stored deputy permissions are not touched.

Documentation: administration/user_soft_delete.md.

SCR-1931

Summary: The SCIM endpoint names its origin in the provisioning log entries and counts its authentications

Effective: 8.54.320 and later

Every SCIM request acts as the context administrator, so the log entries a change produced were indistinguishable from a command line call, and the requests of an identity provider could not be told apart from each other. The endpoint now names itself and the credential it acted with.

  • For the duration of a request the log property com.openexchange.provisioning.origin names the endpoint, the authentication scheme and the credential, for example scim token 7 as oxadmin in context 1; a provisioning token appears by its identifier, a JSON Web Token by its client, basic credentials by their login. The entries of that request carry it, the ones the provisioning services write included. The machine-readable records of extended logging are the exception: they drop the log properties by design.
  • One entry per request goes to the logger com.openexchange.scim.auditTrail, at INFO for the methods that change something and at DEBUG for reads, with method, path, status, duration, caller and, for a create, the location of the new resource. It is a trail of its own, meant to be routed to an appender with additivity="false"; give that appender a pattern with %lmdc, or client address and tracking identifier are lost.
  • New counter appsuite.scim.authentications with the tags scheme (token, basic, jwt, none) and result (accepted, missing, invalid, disabled, forbidden, throttled, unavailable). It tells apart what the status of a request cannot: a wrong password, a token for another context and a scheme switched off by configuration are all answered with 401. Duration, path and status of the requests themselves were already recorded by the REST layer as appsuite.restapi.requests, so no timer was added.
  • A login locked after repeated failed basic authentications is logged with WARN once, when the lock snaps; before, the lock was silent.

Purely additive: no interface changes, no new configuration, and nothing is logged that was not logged before, apart from the new trail and the lock entry.

SCR-1927

Summary: Soft-deleted user state for leavers

Effective: 8.54.320 and later

A user can be soft-deleted, a state between disabled and deleted for the leaver case: the user cannot log in and existing sessions end, is hidden from the global address book, auto-completion, the user listing and group member lists (the memberships are kept), cannot be invited to appointments and answers no free/busy, and the share links and guests the user invited no longer resolve. Mail, files, calendar and contacts are kept and the login name, mail address and aliases stay reserved. mailenabled is left untouched, so a user that was disabled before stays disabled after a restore; the reports count soft-deleted users as disabled. After com.openexchange.user.softDelete.retentionDays (default 30) the clean-up job com.openexchange.admin.softdelete.SoftDeleteRetentionExecution deletes the user for good, hourly on one node of the cluster. The state is set and lifted through the provisioning APIs (RMI, SOAP, gRPC/HTTP) and through SCIM with com.openexchange.scim.deleteMode=softDelete; soft-deleted users are not exposed over SCIM. See administration/user_soft_delete.md.

The deletion for good hands the shared data to the user named when the user was soft-deleted, remembered as the user attribute softDelete/reassignTo; without one it goes to the context administrator as before. The destination is checked again before it is used: one that has been deleted or soft-deleted itself in the meantime falls back to the context administrator rather than failing the purge. Restoring a user forgets the destination; there is no separate call to change it, soft-delete the user again.

SCR-1919

Summary: Removing the calendar access of a shared account permission is persisted when the permission has no mail access

Effective: 8.54.320 and later

OXSharedAccountInterface.setSharedAccountPermissions (RMI, SOAP and gRPC alike) decides per stored permission whether an update has to be written. That decision compared the new calendar configuration against the stored mail configuration instead of the stored calendar configuration. For a permission without mail access, dropping its calendar access therefore compared null with null, counted as unchanged and was never written: the entity kept its calendar access, and the call returned successfully.

The comparison now uses the stored calendar configuration. Permissions with mail access were not affected, since their mail and calendar account identifiers never match. No interface changes.

SCR-1915

Summary: Resource permissions in the provisioning API: returned by getData, validated in simple mode, compared by value

Effective: 8.54.320 and later

Three defects in the handling of resource permissions by the provisioning API (RMI, SOAP and gRPC alike) are fixed; they surfaced while attaching resources to the SCIM service provider.

  • OXResourceInterface.getData for a single resource, and the array form and the gRPC ListResources by identifiers built on it, returned resources without their permissions; the storage applied them to the input object instead of the returned copy. They are now returned, as list and listAll always did.
  • Creating or changing a resource with permissions failed with an internal error whenever com.openexchange.resource.simplePermissionMode was not set explicitly in a properties file: the flag was read without its documented default. It is now read through the lean configuration service, so the default true applies.
  • ResourcePermission compared by identity. The simple-mode validation therefore refused the two configurations it documents, book_directly for group 0 alone and ask_to_book for group 0 together with delegates, and did not recognize the delete marker that resets the permissions. Permissions now compare by entity, kind and privilege, the privilege case-insensitively; the delete marker is recognized by identity only. With the validation working, simple mode now also refuses any list without delegates that is not exactly the default, which it was meant to.

Clients that worked around the failures by not sending permissions are unaffected; clients that sent permissions in simple mode see the documented validation for the first time.

SCR-1886

Summary: Readable 403 page for OpenID Connect logins declined because the user or context is disabled

Effective: 8.54.320 and later

When an OpenID Connect login is declined because the user or the user's context is disabled, the middleware now answers with a readable 403 page telling the user that the account is not available, instead of a bare error page that named the internal user and context identifiers. The identifiers are logged at INFO level instead. The page deliberately offers no link back to the sign-in page: where the sign-in page starts the OpenID Connect flow on its own, such a link only leads back to the identity provider, which authenticates the disabled account again and returns the user to the same page.

The decision is routed through the backend's com.openexchange.oidc.OIDCExceptionHandler, which gains the methods handleContextDisabled(request, response, contextId) and handleUserDisabled(request, response, userId, contextId), mirroring the SAML ExceptionHandler. Both have default implementations rendering the page above, so existing OpenID Connect backends inherit the behavior without changes; a backend can override them to render its own page. The JSON response path is unchanged and keeps answering with LGI-0006.

SCR-1885

Summary: Cache invalidations cross remote Redis sites

Effective: 8.54.320 and later

With remote Redis sites enabled (com.openexchange.redis.sites.enabled), cache invalidations are now repeated on the remote sites' Redis storages, and the messages that invalidate node-local caches of contexts, users, resellers and cache events are published there as well. Two middleware clusters attached to one config database therefore no longer serve stale contexts, users or database assignments after a provisioning change on the other cluster until the cache entries expire. Remote sites are best-effort: an unreachable site is logged, counted in appsuite.redis.remote.failures.total and skipped with a back-off; it never fails the provisioning call. Deployments that only share the databases should set com.openexchange.redis.sites.scope to invalidation to keep sessions on their site. No admin action for deployments without remote sites.

SCR-1883

Summary: Changed creation of the "Collected addresses" folder to the first collected contact instead of login

Effective: 8.54.320 and later

In order to work without a login hook, the contact collector no longer creates a user's "Collected addresses" folder through a login handler at every login. The folder is created the first time the collector has a contact to store, i.e. with the first collected email address that is not yet known as a contact; collector runs that only increment use counts of known contacts create nothing.

  • Until then the user has no "Collected addresses" folder, and modules/mail/contactCollectFolder (io.ox/mail//contactCollectFolder in the JSlob) reports no folder.
  • With the default configuration the switches com.openexchange.user.contactCollectOnMailAccess and com.openexchange.user.contactCollectOnMailTransport are off, so many users only get the folder through an appointment or task with an external participant or through a share notification, and some never do.
  • Deleting the folder works as before; it is recreated with the next collected address instead of at the next login.
  • Existing folders and settings remain valid, no database change or update task is involved, and rolling upgrades are safe. No operator action is required.

Clients that identify the folder through the setting read at login should recognize it through the folder's meta marker __ccf# instead. See the feature documentation for further details.

SCR-1882

Summary: Provisioning read operations now require credentials

Effective: 8.54.320 and later

Provisioning operations that previously answered without checking credentials now require them. This affects the gRPC services DBMigrationService (migration and lock status), LoginCounterService, ShareService (share listings), ConsistencyService and FileChecksumsService (file listings), ExternalAccountService (account listing), DataExportService (task listings) and the access combination lookup of UserService. Registry-wide operations take the master administrator, operations inside a context take that context's administrator; the consistency listings take the master administrator, matching the checkconsistency command-line tool. A caller that sends no or wrong credentials now receives 401 instead of a result. Scripts and monitoring that read these endpoints anonymously have to send credentials from now on. Cross-site forwarding of these read operations is not possible while the underlying RMI methods carry no credentials.

SCR-1881

Summary: Database password no longer part of provisioning responses (HTTP API, SOAP, listdatabase --csv)

Effective: 8.54.320 and later

Provisioning responses no longer carry the password of a registered database. This affects the HTTP API (GET /prov/v1/databases and the register, createSchema and schema-listing responses), the SOAP services (listDatabase, registerDatabase, createSchema, the schema-listing calls and the read and write database inside every returned context) and the command-line tool listdatabase, whose --csv output drops the password column. Registering or changing a database still sends the password, and getDbAccessInfoForSchema of the schema-move interface still returns the access data on purpose. Scripts that read the password from these responses or from the CSV column have to take it from the configdb or from the deployment's configuration instead.

SCR-1859

Summary: Folders shared within an external file storage are stated by the shares action of the folders module

Effective: 8.54.320 and later

The shares action of the folders module states the folders a user shares within an external file storage as well, rather than only the ones of the database. Previously only the storage registered for the requested content type was asked, so the folders of a file storage were not stated at all.

Only a storage that supports being shared that way is affected, so the listing is unchanged for a deployment without such a storage.

SCR-1856

Summary: New file storage service for OpenCloud

Effective: 8.54.320 and later

In order to integrate the files of an OpenCloud instance, the new file storage service opencloud is introduced. It accesses the files through WebDAV, and the spaces, shares and permissions of the instance through the libre graph API, authenticating either with a username and an app token, or through OAuth.

The service is available for the users the capability com.openexchange.capability.filestorage_opencloud is enabled for. An account states the personal space of the user, the project spaces below a virtual folder Spaces, the shared items below a virtual folder Shared, and the trash bins of the spaces below a virtual folder Trash. As OpenCloud limits the storage per space, the quota of a folder is the one of the space it belongs to. As it grants rights through roles, a share states the role that matches the requested permissions best, so a file that is shared for writing is shared with a role that allows deleting it as well, there being no role that allows the one without the other. A share that is meant to be read-only is never granted a role that allows to change the item. An item states the URL that opens it within the web interface of the instance, and a folder states whether it supports permissions, a quota and sorting.

See the feature documentation for further details.

SCR-1851

Summary: Bounded the init container's configdb and middleware readiness waits

Effective: 8.54.320 and later

In order to let an unreachable database fail visibly instead of hanging a pod indefinitely, the core-mw init container now bounds its readiness waits for the configdb and, during initial bootstrapping, for the middleware itself. Previously both the legacy bash and the Go init variant retried forever without sleeping, so a misconfigured MYSQL_HOST left the pod in Init:0/1 with no non-zero exit and no Kubernetes event, while the retry loop consumed a full CPU core. Once a timeout elapses, the init container names the unreachable target and exits non-zero. The reason is also written to the pod's termination log, so it stays readable in the pod status after the restart that follows.

While a wait is running, progress is reported every 30 seconds, for example Still waiting for the MySQL Server at db:3306 after 31s of 300s, so a slow start is visible instead of silent.

A configdb host name that does not resolve is reported after a separate, shorter grace period of 30 seconds, because an unresolvable name is a configuration error rather than a database that is slow to start. The grace period exists because on a fresh install the name legitimately stays unresolvable for a while, for instance while cluster DNS is still starting or a headless service has no endpoints yet. Temporary resolver failures do not count towards it.

Two new Helm chart values control the budgets, both in seconds:

  • initWait.dbTimeout, default 300, passed to the init container as INIT_DB_WAIT_TIMEOUT
  • initWait.middlewareTimeout, default 300, passed as INIT_MW_WAIT_TIMEOUT

The poll intervals and the DNS grace are not exposed as chart values; they are fixed at 2 seconds, 5 seconds for the middleware wait, and 30 seconds respectively. The Go init binary additionally honors INIT_DB_WAIT_INTERVAL, INIT_MW_WAIT_INTERVAL and INIT_DB_DNS_TIMEOUT if they are set by hand, where 0 disables the early exit on an unresolvable name; these accept a Go duration string such as 30s or 2m, and a plain number is read as seconds.

No operator action is required, the values are additive and defaulted. Deployments that legitimately need to wait longer than five minutes for their database have to raise initWait.dbTimeout; a sufficiently high value restores the previous, effectively unbounded behavior.

CLT

SCR-1980

Summary: Added command-line tool jfrrecord for on-demand flight recordings

Effective: 8.54.320 and later

The new command-line tool jfrrecord records a Java Flight Recorder profile on a running middleware node for a fixed duration and writes the file on the host running the tool. It needs no restart and no agent, leaves nothing on the middleware's host, and requires the master administrator's credentials.

  • -f/--file and --overwrite: the target file
  • -d/--duration: 60 seconds by default, at most 10 minutes
  • --settings: default or profile
  • --method-timing: times the given methods (event jdk.MethodTiming)
  • --cpu-time: samples CPU time, Linux only

Each node runs at most one recording. It stops after its duration, is capped at 256 MB, and is discarded when the tool terminates or has not polled for 2 minutes. System properties, environment variables, JVM arguments and process command lines are not recorded.

Method timing requires org.osgi.framework.bootdelegation=jdk.jfr.tracing. The shipped config.ini template now sets it; deployments with their own config.ini have to add it.

Usage:

$ jfrrecord --help
usage: jfrrecord
 -A,--adminuser <adminUser>       Admin username
    --cpu-time                    Additionally samples CPU time (event jdk.CPUTimeSample, experimental). Linux only
 -d,--duration <duration>         How long to record, in seconds ("90" or "90s") or minutes ("5m"). Default is 60
                                  seconds, maximum is 10 minutes
 -f,--file <file>                 The path name of the file to write the recording to; e.g. "/tmp/recording.jfr". The
                                  file is written where this tool runs
 -h,--help                        Prints this help text
 -H,--host <jmxHost>              The optional JMX host (default:localhost)
 -l,--login <jmxLogin>            The optional JMX login (if JMX authentication is enabled)
    --method-timing <filters>     Times invocations of the given methods (event jdk.MethodTiming). Semicolon-separated
                                  fully qualified class names, each optionally followed by "::" and a method name; e.g.
                                  "com.openexchange.drive.impl.DriveServiceImpl::syncFolders". At most 10 filters.
                                  Requires "org.osgi.framework.bootdelegation=jdk.jfr.tracing" on the middleware
    --overwrite                   Overwrite the file if it already exists
 -p,--port <jmxPort>              The optional JMX port (default:9999)
 -P,--adminpass <adminPassword>   Admin password
    --responsetimeout <timeout>   The optional response timeout in seconds when reading data from server (default: 0s;
                                  infinite)
 -s,--password <jmxPassword>      The optional JMX password (if JMX authentication is enabled)
    --settings <settings>         The JFR configuration: "default" (default, low overhead) or "profile" (finer sampling,
                                  more overhead)

The Open-Xchange flight recording tool

SCR-1966

Summary: New command line tool detachprovisioningtoken

Effective: 8.54.320 and later

The new command line tool detachprovisioningtoken ends one context's exposure to a cross-context provisioning token: the token stops opening the context given with -c, keeps working for every other context it opens, and is deleted only if this was the last one. A cross-context token is issued by an administrator standing above the contexts, so the context reached into has no part in it; listprovisioningtokens with --reaching-into shows which tokens open a context, and this tool ends such an access. It is not a revocation - revokeprovisioningtoken ends a token everywhere at once - and no lasting veto, since an administrator above the contexts may issue a token covering the context again. Detaching a context the token does not open is reported on the error stream and ends with exit code 0. It ships with open-xchange-admin next to the other administration tools and calls the RMI interface OXProvisioningTokenInterface, so it needs neither the HTTP gateway nor gRPC access; authorization follows every other operation inside a context: the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator.

Usage: detachprovisioningtoken
 -h,--help                                        Prints a help text
    --environment                                 Show info about commandline environment
    --nonl                                        Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>           response timeout in seconds for reading response from the backend (default 0s; infinite)
 -A,--adminuser <adminuser>                     ? Admin username
 -P,--adminpass <adminpass>                     ? Admin password
 -c,--contextid <contextid>                     * The context to detach
    --token-id <id>                             * The identifier of the token, as listprovisioningtokens --reaching-into shows it

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1942

Summary: New command line tool revokeprovisioningtoken

Effective: 8.54.320 and later

The new command line tool revokeprovisioningtoken revokes a provisioning token given by --token-id, the identifier listprovisioningtokens shows; its secret is refused from then on. Given a context with -c, a token bound to that context is revoked; without a context, a cross-context token, which only the master administrator or, where MASTER_ACCOUNT_OVERRIDE permits it, a reseller administrator owning every one of the contexts may revoke. A token that does not exist, or lies out of the caller's reach, is reported on the error stream, so a revocation that did not happen is not mistaken for one that did, and ends with exit code 0, like deletesecondaryaccount does for an unknown account. It ships with open-xchange-admin next to the other administration tools and calls the RMI interface OXProvisioningTokenInterface, so it needs neither the HTTP gateway nor gRPC access; authorization for a context follows every other operation inside a context: the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator.

Usage: revokeprovisioningtoken
 -h,--help                                        Prints a help text
    --environment                                 Show info about commandline environment
    --nonl                                        Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>           response timeout in seconds for reading response from the backend (default 0s; infinite)
 -A,--adminuser <adminuser>                     ? Admin username
 -P,--adminpass <adminpass>                     ? Admin password
 -c,--contextid <contextid>                       The id of the context
    --token-id <id>                             * The identifier of the token, as listprovisioningtokens shows it

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1941

Summary: New command line tool listprovisioningtokens

Effective: 8.54.320 and later

The new command line tool listprovisioningtokens lists provisioning tokens, expired ones included: identifier, the contexts a token opens, label, scope, creator, creation time, expiration time and last successful use, as ISO-8601 in UTC. Given a context with -c, the tokens bound to that context are listed; without a context, the cross-context tokens the caller stands above: all of them for the master administrator, which needs no MASTER_ACCOUNT_OVERRIDE, and for a reseller administrator those opening only contexts it owns, which needs it as every reseller access to a context does. Secrets are never shown.--csv gives CSV output, with empty fields where a token does not expire or was never used. Given a context together with --reaching-into, the listing runs the other way round: the cross-context tokens that open that context, which its administrator has no other way to see; each is shown naming that context and no other. It ships with open-xchange-admin next to the other administration tools and calls the RMI interface OXProvisioningTokenInterface, so it needs neither the HTTP gateway nor gRPC access; authorization for a context follows every other operation inside a context: the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator.

Usage: listprovisioningtokens
 -h,--help                                        Prints a help text
    --environment                                 Show info about commandline environment
    --nonl                                        Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>           response timeout in seconds for reading response from the backend (default 0s; infinite)
 -A,--adminuser <adminuser>                     ? Admin username
 -P,--adminpass <adminpass>                     ? Admin password
 -c,--contextid <contextid>                       The id of the context
    --csv                                         Format output to csv

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1940

Summary: New command line tool createprovisioningtoken

Effective: 8.54.320 and later

The new command line tool createprovisioningtoken creates a provisioning token, the bearer secret an automated client such as an identity provider driving the SCIM service provider or a script driving the provisioning API authenticates with. Given a context with -c, the token is bound to that context; given --contexts with at least two context identifiers instead, a cross-context token is created that opens every one of them, which only the master administrator or a reseller administrator owning every one of the contexts, and only where MASTER_ACCOUNT_OVERRIDE lets an administrator reach into a context at all may do. --label says what the token is for, --scope names the interface it opens, scim or provisioning (defaults to scim for a context and is mandatory together with --contexts, so that moving a client from one context to several cannot silently change what its token opens), and --expires takes a date as yyyy-MM-dd (midnight UTC) or a date and time with offset such as 2027-01-01T12:00:00Z; without it the token does not expire. The tool prints the identifier and, this once, the secret of the form ox_<context-id>_<64 hex characters>, or ox_x_<64 hex characters> for a cross-context token. It ships with open-xchange-admin next to the other administration tools and calls the RMI interface OXProvisioningTokenInterface, so it needs neither the HTTP gateway nor gRPC access; authorization for a context follows every other operation inside a context: the context administrator, a reseller administrator owning the context or, where MASTER_ACCOUNT_OVERRIDE permits it, the master administrator.

Usage: createprovisioningtoken
 -h,--help                                        Prints a help text
    --environment                                 Show info about commandline environment
    --nonl                                        Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>           response timeout in seconds for reading response from the backend (default 0s; infinite)
 -A,--adminuser <adminuser>                     ? Admin username
 -P,--adminpass <adminpass>                     ? Admin password
 -c,--contextid <contextid>                     | The id of the context
    --contexts <contexts>                       | The contexts a cross-context token opens, comma-separated, at least two; instead of a single context. Needs the master administrator or a reseller administrator owning every one of them
    --label <label>                             * What the token is for, for example the identity provider using it; at most 128 characters
    --scope <scope>                               The interface the token opens: scim or provisioning. Defaults to scim for a context; mandatory together with --contexts
    --expires <date>                              When the token expires: yyyy-MM-dd (midnight UTC) or a date and time with offset such as 2027-01-01T12:00:00Z. Without, it does not expire

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1930

Summary: New option --soft-deleted for listuser

Effective: 8.54.320 and later

listuser gains the option --soft-deleted that lists the soft-deleted users of the context only; the search pattern and the guest options do not apply to it. The ordinary listing keeps including soft-deleted users. The CSV output of the user listings (--csv) carries the new column SoftDeleted, the date of the soft-deletion, empty for every other user.

Usage: listuser 
 -h,--help                                         Prints a help text          
    --environment                                  Show info about commandline environment
    --nonl                                         Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>            response timeout in seconds for reading response from the backend (default 0s; infinite)
 -c,--contextid <contextid>                      * The id of the context       
 -A,--adminuser <adminuser>                      ? Admin username              
 -P,--adminpass <adminpass>                      ? Admin password              
    --csv                                          Format output to csv        
    --includeguests                                Include guest users         
    --excludeusers                                 Exclude users, only show guests
    --soft-deleted                                 List the soft-deleted users of the context only; the search pattern and the guest options do not apply.
 -s,--searchpattern <searchpattern>                The search pattern which is used for listing. This applies to name.
 -i,--ignorecase                                   Whether to perform look-up case-insensitive
    --length <length>                              Limit result size           
    --offset <offset>                              Set offset for limited result size

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1929

Summary: New command line tool restoreuser

Effective: 8.54.320 and later

The new command line tool restoreuser lifts the soft-deleted state set by softdeleteuser: the user appears in the address book and in group member lists again, can be invited again and, unless disabled through mailenabled, can log in again. A user that is not soft-deleted is rejected. The user is addressed by -i or -u in the context given by -c.

Usage: restoreuser 
 -h,--help                                         Prints a help text          
    --environment                                  Show info about commandline environment
    --nonl                                         Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>            response timeout in seconds for reading response from the backend (default 0s; infinite)
 -c,--contextid <contextid>                      * The id of the context       
 -A,--adminuser <adminuser>                      ? Admin username              
 -P,--adminpass <adminpass>                      ? Admin password              
 -i,--userid <userid>                            | Id of the user              
 -u,--username <username>                        | Username of the user        

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

SCR-1928

Summary: New command line tool softdeleteuser

Effective: 8.54.320 and later

The new command line tool softdeleteuser marks a user as soft-deleted, the leaver state between disabled and deleted: the user cannot log in, is hidden from the address book and from group member lists, cannot be invited, and the share links and guests the user invited stop working, while mail, files, calendar and contacts are kept and the login name stays reserved. After com.openexchange.user.softDelete.retentionDays the user is deleted for good; until then restoreuser brings the user back. The context administrator, guests, shared accounts and users that are already soft-deleted are rejected. The user is addressed like with deleteuser, by -i or -u in the context given by -c.

Usage: softdeleteuser 
 -h,--help                                         Prints a help text          
    --environment                                  Show info about commandline environment
    --nonl                                         Remove all newlines (\n) from output
    --responsetimeout <responsetimeout>            response timeout in seconds for reading response from the backend (default 0s; infinite)
 -c,--contextid <contextid>                      * The id of the context       
 -A,--adminuser <adminuser>                      ? Admin username              
 -P,--adminpass <adminpass>                      ? Admin password              
 -i,--userid <userid>                            | Id of the user              
 -u,--username <username>                        | Username of the user        
 -r,--reassign <reassign>                          The user id shared data will be assigned to once the retention period has passed. If omitted the context admin will be used instead.
    --no-reassign                                  If set all shared data will be deleted instead of being assigned once the retention period has passed.

Entries marked with an asterisk (*) are mandatory.
Entries marked with an question mark (?) are mandatory depending on your
configuration.
Entries marked with a pipe (|) are mandatory for one another which means that
at least one of them must be set.

The options -r/--reassign <id> and --no-reassign name the user the shared data is reassigned to once the retention period has passed, exactly as on deleteuser: --reassign hands it to that user, --no-reassign drops it, neither leaves it to the context administrator. The destination is checked when it is named and again before it is used.

Changed defaults

SCR-1998

Summary: More carrier threads for virtual threads by default

Effective: 8.54.320 and later

The middleware now starts with -Djdk.virtualThreadScheduler.parallelism=256, the number of carrier threads its virtual threads run on, unless a JAVA_OPTS_* value already sets it. A virtual thread that loads a class is pinned to its carrier: with no more carriers than CPU cores, requests loading the same class at once could pin them all while the holder of a class loader lock waited for a carrier, and the middleware stopped answering. Carrier threads are ordinary platform threads, and idle ones cost little. Set the option in JAVA_OPTS_OTHER or in ox-scriptconf.sh to choose another value.

Configuration

SCR-2000

Summary: New configuration options for anti-virus scanning

Effective: 8.54.320 and later

New options to enforce anti-virus scans and to secure and bound the connection to the ICAP service.

  • com.openexchange.antivirus.enforce Scans every download of a user who has the anti-virus feature enabled, regardless of the client's scan parameter. Items that cannot be scanned are refused, as is every download while the anti-virus service is unavailable. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.icap.client.connectTimeout Time-out in milliseconds for establishing the connection to the ICAP server. A value less than or equal to 0 leaves it to the operating system. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.icap.client.requestTimeout Maximum time in milliseconds a single ICAP request may take once connected, sending the item included. A value less than or equal to 0 imposes no limit. Default 120000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.icap.client.tls Contacts the ICAP server via TLS. Certificate and host name are always verified, against com.openexchange.icap.client.tls.truststore or the JDK's default trust store, independent of com.openexchange.net.ssl.trustlevel. Default false. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.icap.client.tls.truststore Path of a PKCS#12 or JKS trust store for TLS connections to the ICAP server, e.g. holding a custom CA. If empty, the JDK's default trust store is used. Default empty. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.icap.client.tls.truststorePassword Password of the trust store given by com.openexchange.icap.client.tls.truststore. Default empty. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1996

Summary: New configuration options for the Mobile API

Effective: 8.54.320 and later

The following options control the Mobile API at /mobile/v1. All are reloadable and have no dedicated properties file.

  • com.openexchange.mobile.api.enabled

Whether the API answers. Default false. Config-cascade aware: the server-level value decides whether /mobile/v1 answers at all, a lower level whether a signed-in user may use it.

  • com.openexchange.mobile.auth.mode

How apps sign in: token for personal access tokens, idp for the operator's identity provider. Default token.

  • com.openexchange.mobile.auth.idp.issuer

Mode idp: the OpenID Connect issuer published by auth/config. Empty by default. Without it and com.openexchange.mobile.auth.idp.clientId, auth/config answers 500.

  • com.openexchange.mobile.auth.idp.clientId

Mode idp: the public client identifier of the app. Empty by default.

  • com.openexchange.mobile.auth.idp.scopes

Mode idp: the scopes, comma-separated, the app requests. Empty by default.

  • com.openexchange.mobile.auth.idp.audience

Mode idp: the resource indicator (RFC 8707) the app passes, if the identity provider needs one. Empty by default. It is published only; a token's audience is checked when com.openexchange.oauth.provider.jwt.audience is set.

  • com.openexchange.mobile.auth.idp.redirectUris.ios, com.openexchange.mobile.auth.idp.redirectUris.android

Mode idp: the redirect URIs of the two apps. Empty by default.

SCR-1994

Summary: New configuration options for sending a mail at most once

Effective: 8.54.320 and later

The following options control the Idempotency-Key header and the client message identifier of mail send requests.

  • com.openexchange.ajax.idempotency.enabled Whether actions honor the Idempotency-Key header. Default true. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.ajax.idempotency.retention How long the result of a request other than a send is kept for a retry. Default 24h. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.ajax.idempotency.sendRetention How long the result of sending a mail is kept for a retry. Default 7d. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.ajax.idempotency.maxResultSize The largest result kept for a retry; 0 keeps none. Default 64KB. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.ajax.idempotency.claimTimeout How long the key of a running request lives without being renewed; at least 30s. Default 5m. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.sentMessageIds.enabled Whether a mail with a client message identifier is sent at most once. Default true. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.sentMessageIds.retention How long the client message identifier of a sent mail is remembered; each takes about 0.3 KB in Redis. Default 30d. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1992

Summary: New configuration options for signing in for a personal access token

Effective: 8.54.320 and later

The following options control the sign-in with user name and password for a personal access token through login?action=accessToken.

  • com.openexchange.accesstoken.signin.enabled Whether a client may sign in for a token. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.defaultScopes The scopes, comma-separated, a token gets when the client names none; scopes the user cannot grant are left out. Default read_mail,write_mail,read_contacts. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.defaultLifetimeDays The lifetime in days of a token when the client names no expiry, capped by com.openexchange.accesstoken.maxLifetimeDays. A value less than or equal to 0 (zero) is ignored and the default is used. Default 90. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.allowedClients The client identifiers, comma-separated, that may sign in; empty allows every client. Default empty. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.challengeLifetime How long in milliseconds a user with a second factor has to confirm the sign-in; the password is kept sealed for that time. A value less than or equal to 0 (zero) or above one hour is ignored and the default is used. Default 300000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.rateLimit.perLogin How many sign-in attempts a login name may make in the time window, counted cluster-wide before the password is checked; the same number bounds the second-factor codes and SMS per user. A sign-in with the full password or a confirmed second factor clears the count. 0 or less disables this limit. Default 5. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.rateLimit.perIp How many failed sign-ins a client address may have in the time window, counted cluster-wide; the address is the transport address, IPv6 per /64 network. 0 or less disables this limit. Default 20. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.accesstoken.signin.rateLimit.timeWindow The time window in milliseconds of the limits, between 10 seconds and 12 hours; values outside are clamped. 0 or less disables the limits per login name and address. Default 900000. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1989

Summary: New plural existing*Secrets list values in the core-mw Helm chart

Effective: 8.54.320 and later

In order to take sensitive configuration from several Secrets, for example one ExternalSecret per credential, without merging them into one Secret first, every additive existing*Secret value of the core-mw Helm chart gets a plural twin that accepts a list of further Secret names in the release namespace:

  • existingPropertiesSecrets
  • existingUISettingsSecrets
  • existingMetaSecrets
  • existingContextSetsSecrets
  • existingETCFilesSecrets
  • existingETCBinariesSecrets
  • existingYAMLFilesSecrets
  • existingEnvSecrets

The singular value stays supported and is mounted first; a name given twice is used once. The file-based lists are projected into the same directory as the singular Secret, so the keys of all listed Secrets have to be distinct, and a duplicate property is still decided by the numeric file-name prefix, not by list order. Entries of existingEnvSecrets become further envFrom sources after existingEnvSecret; a later entry wins on a duplicate key, and extraEnv wins over all of them. The lists can be set per role or per node type under roles.<role>.values and scaling.nodes.<type>.values, where they replace the global list rather than extend it. Adding, removing or editing a listed Secret changes the checksum/existingSecrets pod annotation and rolls the pods. All lists default to empty; existing deployments are unaffected and do not roll on upgrade. existingASConfigSecret, redis.existingSecret and mysql.existingSecret stay singular.

SCR-1982

Summary: The folders of a Nextcloud account are arranged like the ones of an OpenCloud account

Effective: 8.54.320 and later

The root folder of a Nextcloud account no longer holds the files of the user directly. It holds the virtual folders Personal, Shares and Team folders, along with Trash, where Shares holds Shared with me, Shared with others and Shared via link. The items of the user are listed within Personal, Shared with me or Team folders, depending on the way the instance mounts them, so the identifier of an item is unchanged - only the folder it states as its parent is.

Within the meta of a folder, the object nextcloud states:

  • folderType - the kind of the folder, one of personal, shared, sharedWithMe, sharedWithOthers, sharedViaLink, teamFolders, teamFolder and trash, absent for a folder of the user.

  • quota - the quota of the storage the folder denotes, stated for Personal and for each team folder, holding total and used in bytes, where a storage the instance does not limit states -1 as its total.

  • backwardLink - the link that opens the folder within the web interface of the instance, which a file states within its meta as well.

The virtual folders are of the type VIRTUAL_FOLDER, so a traversal of the folder tree passes them over, see https://jira.atlassian.open-xchange.com/browse/SCR-1970 .

SCR-1981

Summary: New properties of the OAuth provider of a Nextcloud instance

Effective: 8.54.320 and later

An account of the Nextcloud file storage may be linked to an OAuth account from now on, see https://jira.atlassian.open-xchange.com/browse/SCR-1981 , which the new OAuth service with the service identifier nextcloud is introduced for. It is configured through the following properties, all of which are reloadable and evaluated through the config cascade, so a deployment may integrate a different instance per context or user:

  • com.openexchange.oauth.nextcloud.enabled, default false, states whether the service is offered at all.
  • com.openexchange.oauth.nextcloud.hostname, empty by default, states the host of the instance the deployment integrates with. It is required if the service is enabled.
  • com.openexchange.oauth.nextcloud.apiKey and com.openexchange.oauth.nextcloud.apiSecret, both empty by default, state the client the deployment authenticates with. Both are required: as opposed to the OpenCloud provider, the Nextcloud one authenticates as a confidential client only, so a client without a secret is not supported and the service is not offered until both hold a value.
  • com.openexchange.oauth.nextcloud.redirectUrl and com.openexchange.oauth.nextcloud.productName, the ordinary properties of an OAuth service.
  • com.openexchange.oauth.nextcloud.authorizationUrl, com.openexchange.oauth.nextcloud.tokenUrl and com.openexchange.oauth.nextcloud.userInfoUrl, empty by default, state the endpoints of an identity provider in front of the instance, e.g. Keycloak. Left empty, the endpoints of the instance itself are used.
  • com.openexchange.oauth.nextcloud.scope, default openid profile email offline_access, states the scopes that are requested when the deployment is authorized, separated by spaces.

An account that authenticates with a username and an app token needs none of these, the OAuth service being optional for the storage. The storage is registered regardless, as long as the package open-xchange-oauth is enabled, see SCR-1855.

SCR-1978

Summary: New configuration options for live change events

Effective: 8.54.320 and later

The new push notification transport sse delivers notifications to live change streams (Server-Sent Events). The following options control it.

  • com.openexchange.pns.transport.sse.enabled Whether the transport is enabled. It can be refined per client and topic by appending .<client> and .<client>.<topic>. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.pingInterval The interval in milliseconds in which an open stream refreshes its presence and sends a ping event to the client; a presence that is not refreshed expires after twice this interval. Default 30000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerUser The maximum number of streams a user may have open across the cluster. Opening one more closes the user's oldest stream with reason replaced. A value less than or equal to 0 (zero) disables the limit. Default 5. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxConnectionsPerNode The maximum number of streams open on one node. Further requests are rejected with HTTP status 503 and a Retry-After header, so the client can reconnect to another node. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.maxLifetime The maximum lifetime of a stream in milliseconds. Once reached, the stream ends with reason lifetime and the client reconnects. A value less than or equal to 0 (zero) is ignored and the default is used. Default 1800000 (30 minutes). Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.pns.transport.sse.tokenCheckInterval The interval in milliseconds in which the bearer token of a stream is validated again; a stream whose token was revoked or expired then ends with reason token_invalid. Keep it below 5 minutes, the time an idle OAuth session lives. A value less than or equal to 0 (zero) is ignored and the default is used. Default 120000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.mode How changes made outside the middleware, e.g. by another mail client, are noticed for live change streams of users whose primary account is IMAP. poll looks at the mailbox of a user with an open stream once per pollInterval, in one LIST "" "*" RETURN (STATUS (MESSAGES UIDNEXT UIDVALIDITY HIGHESTMODSEQ)) command on a pooled connection; it needs the IMAP extensions LIST-EXTENDED and LIST-STATUS, and CONDSTORE for changes that leave the message counts alone. off leaves such changes unnoticed. Changes made through the middleware are reported either way. Default poll. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.pollInterval The interval in milliseconds in which such a mailbox is looked at. Streams are handled every 5 seconds, so shorter values take effect as 5 seconds. Default 10000. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.imap.liveChanges.maxWatches The maximum number of users a node watches this way. Streams of further users still report the changes made through the middleware. A value less than or equal to 0 (zero) disables the limit. Default 5000. Reloadable, not config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.liveChanges.echoWindow The time in milliseconds within which the mail server’s report of a change the middleware made itself is left out, so that a live change stream sees such a change once. It has to exceed the time the mail server needs to report a change, e.g. com.openexchange.imap.liveChanges.pollInterval. A value less than or equal to 0 (zero) switches the suppression off and reports the change twice. Default 15000. Reloadable, config-cascade aware. No dedicated properties file.

SCR-1975

Summary: New Properties to Configure Default Alarms for Newly Provisioned Calendar Accounts

Effective: 8.54.320 and later

Two new lean configuration properties define a default alarm that is taken over into a user's calendar settings when the internal calendar account is provisioned:

  • com.openexchange.calendar.defaultAlarmDate for appointments whose start date is of type date, i.e. all-day appointments
  • com.openexchange.calendar.defaultAlarmDateTime for appointments whose start date is of type date-time

Each value is a trigger duration relative to the start of the appointment as defined in RFC 5545, e.g. -PT15M or PT9H, and yields a single alarm with action DISPLAY. Both default to an empty value, are reloadable and config-cascade aware down to scope user. Existing accounts are not touched, and a legacy defaultReminder setting already stored for the user takes precedence. No operator action is required; without a value there is no default reminder, as before.

See the property documentation for further details.

SCR-1971

Summary: New properties matching the users and groups of a Nextcloud instance with the ones of the server

Effective: 8.54.320 and later

The Nextcloud file storage states the shares of an item as its permissions, and applies the permissions a client states as shares, which requires the users and groups of the instance to be matched with the ones of the server. Four properties are introduced for that, all of them reloadable and evaluated through the config cascade:

  • com.openexchange.file.storage.nextcloud.entityResolver.attribute, default mail, states whether a user is matched by its mail address or by the name it logs in with, the respective other one being used as a fallback.

  • com.openexchange.file.storage.nextcloud.entityResolver.mappingFile, empty by default, states the path to a file that maps the entities of both systems statically, which takes precedence over matching by attribute. It states one entry per line, ':'-separated: contextId:nextCloudUserId:oxUserId for a user, group:contextId:nextCloudGroupId:oxGroupId for a group.

  • com.openexchange.file.storage.nextcloud.entityResolver.folderPermissions, default false, expresses the shares of a listed folder as its permissions, which is a request per shared folder.

  • com.openexchange.file.storage.nextcloud.entityResolver.objectPermissions, default false, expresses the shares of a listed file as its object permissions, which is a request per shared file.

  • com.openexchange.file.storage.nextcloud.retryAfterErrorInterval, default 300, the period, in seconds, an account whose credentials the Nextcloud instance rejected is not asked again for.

Disabled, a shared item states that it is shared, but not with whom. Neither property applies to the permissions a client states for an item, which are applied to its shares in any case.

A deployment that registers a NextCloudEntityResolver of its own supersedes the default implementation, which is registered with the default service ranking.

SCR-1967

Summary: New properties to configure authentication and TLS for the Cassandra connection

Effective: 8.54.320 and later

In order to connect to Cassandra clusters that require authentication or encryption in transit, the connection opened by bundle com.openexchange.nosql.cassandra is now configurable through new lean configuration properties:

  • com.openexchange.nosql.cassandra.username - the user name to authenticate with; empty by default, in which case no authentication is performed at all
  • com.openexchange.nosql.cassandra.password - the accompanying password
  • com.openexchange.nosql.cassandra.authProviderClass - the driver's authentication provider, PlainTextAuthProvider by default
  • com.openexchange.nosql.cassandra.ssl - encrypts the connections using TLS, false by default
  • com.openexchange.nosql.cassandra.sslKeystorePath and com.openexchange.nosql.cassandra.sslKeystorePassword - the key store holding the client certificate, for clusters that require client certificate authentication

Encryption follows the server's central SSL configuration: the trust material, the enabled protocols and the cipher suites are taken from the properties with prefix com.openexchange.net.ssl., and the node's host name is matched against its certificate unless com.openexchange.net.ssl.hostname.verification.enabled is turned off. A certificate authority that is unknown to the JVM therefore belongs into the central custom trust store; there is no Cassandra-specific trust store.

None of the properties is reloadable or config-cascade aware; they are evaluated once when the session is built, so a changed credential requires a restart. The change is purely additive - with an empty user name and TLS disabled the resulting driver configuration is unchanged, so existing installations are unaffected.

Note that -Ddatastax-java-driver.advanced.auth-provider.* JVM arguments continue to take precedence over the authentication properties. A deployment still passing credentials that way keeps working, but a leftover argument silently overrides the configured value.

A rejected credential is now reported as an authentication error instead of unreachable contact points, and is retried on the same timer, so that a secret which has not been rolled out yet degrades the node instead of failing bundle start.

SCR-1955

Summary: New configuration options for the mail credential vault's token exchange

Effective: 8.54.320 and later

The mail credential vault keeps what lets a feature reach a mailbox without a live session - snoozed mail, scheduled mail, the GDPR mail export and permanent push. These options say that what it keeps is obtained through an OAuth token exchange (RFC 8693) rather than taken from the session as it is. Each of them also exists with a feature identifier appended - snoozed-mail, scheduled-mail, gdpr-export or push - and that value wins for that feature, so a deployment configures the exchange once and can still say something different about a single one. A feature added later needs no option of its own.

  • com.openexchange.mail.credential.oauth.tokenExchange Whether the credentials kept for a feature come from a token exchange. Set it to false behind a feature identifier to leave that feature out of what all others do; left empty there, the feature inherits. Where a deployment configured com.openexchange.mail.snoozed.oauth.tokenExchange or com.openexchange.mail.scheduled.oauth.tokenExchange before these options existed, those values are still read and take precedence over the shared one, so nothing has to be migrated. Default false. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.credential.oauth.tokenExchange.backendPath The path of the OpenID Connect back-end to exchange with. Empty means the back-end the session itself came from. Default empty. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.credential.oauth.tokenExchange.scope The OAuth scope to request. Empty means none is requested. Worth setting per feature, since reaching a mailbox needs less than sending through it. Default empty. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.mail.credential.oauth.tokenExchange.additionalParameters Further parameters for the exchange request, as a single key=value pair; multiple pairs are separated by &. Typically selects the exchange policy at the OAuth server, e.g. usecase=mail-snooze, which is per feature by nature. Empty means none are sent. Default empty. Reloadable, config-cascade aware. No dedicated properties file.

SCR-1954

Summary: New configuration options for the Web Push transport and WebDAV-Push

Effective: 8.54.320 and later

New configuration for the Web Push transport of the push notification service (bundle com.openexchange.pns.transport.webpush, package open-xchange-pns-impl) and for the WebDAV-Push extension for CalDAV and CardDAV (bundle com.openexchange.dav.push, package open-xchange-dav). All properties are lean properties with built-in defaults and no properties file; the feature is off by default.

  • com.openexchange.pns.transport.webpush.*: enabling the transport (with the usual per-client and per-topic suffixes), the VAPID key - preferably as a Kubernetes secret through the key store service, alternatively as properties -, the egress policy for the client-supplied push resources (HTTPS only, host allowlist, local endpoints, "trust all" handling), the TTL and Urgency headers, the maximum lifetime of a subscription and the number of subscriptions a user may hold.
  • com.openexchange.httpclient.webpush.*: the managed HTTP client the transport delivers with, tuned through the generic HTTP client keys; the transport ships with its own defaults (short timeouts, larger pool, hard timeouts enabled).
  • com.openexchange.dav.push.*: the config-cascade aware switch offering WebDAV-Push to CalDAV/CardDAV clients (default false), the maximum lifetime of WebDAV-Push subscriptions, and the secret the opaque collection topics are derived with.

Rotating the VAPID key invalidates the existing subscriptions at the push services until the clients re-register with the new key; all nodes of a cluster must use the same key.

See the general documentation, as well as the configuration documentation for the Web Push transport and for WebDAV-Push for further details.

SCR-1934

Summary: Removed the property com.openexchange.antivirus.mode along with the experimental streaming operation mode

Effective: 8.54.320 and later

The property com.openexchange.antivirus.mode (default double-fetch) is no longer evaluated and has been removed; deployments still setting it can simply drop it. Its experimental streaming value never returned the scanned content to the requesting client and left the ICAP connection open after every scan, so the mode has been removed along with the property. Anti-Virus scanning now always behaves as it did with the previous default.

The Java method AntiVirusService.canStream(), which only reported that mode, has been removed as well.

SCR-1926

Summary: New configuration options for soft-deleted users

Effective: 8.54.320 and later

Options for the soft-deleted user state, the leaver state between disabled and deleted.

  • com.openexchange.user.softDelete.retentionDays The number of days a soft-deleted user is kept before the clean-up job com.openexchange.admin.softdelete.SoftDeleteRetentionExecution deletes the user for good, the same way an explicit delete through the provisioning API would. 0 keeps soft-deleted users until an administrator deletes or restores them. Default 30. Reloadable, config-cascade aware. No dedicated properties file.

  • com.openexchange.scim.deleteMode Accepts the additional value softDelete: a SCIM DELETE of a user soft-deletes the user instead of deactivating or deleting it. Default deactivate, unchanged. Reloadable, config-cascade aware. No dedicated properties file.

SCR-1912

Summary: New property com.openexchange.oauth.provider.jwt.audience; EC and RSA-PSS signatures accepted

Effective: 8.54.320 and later

Two changes to the OAuth provider's validation of JSON Web Tokens in mode expect_jwt, closing gaps found while attaching the SCIM service provider to it.

  • New property com.openexchange.oauth.provider.jwt.audience: a comma separated list of audiences (claim aud) tokens are accepted for; a token has to carry one of them. Empty, the default, keeps the audience unchecked, as before. Reloadable and config-cascade aware. Operators are advised to set it, so that only tokens minted for the deployment pass.
  • Tokens signed with the RSA-PSS (PS256/384/512) and EC (ES256/384/512) algorithms are accepted next to RS256/384/512, according to the key type in the JWK set. Symmetric algorithms and none remain refused.
  • An empty com.openexchange.oauth.provider.allowedIssuer still accepts every issuer whose key the JWK set holds, but is now logged as a warning on start and reload.

SCR-1911

Summary: New property com.openexchange.scim.allowJwt

Effective: 8.54.320 and later

com.openexchange.scim.allowJwt defines whether the SCIM endpoint accepts JSON Web Tokens issued by the identity provider next to provisioning tokens. The token is validated by the OAuth provider (com.openexchange.oauth.provider.mode=expect_jwt), has to belong to the context, carry the scope scim and name the context administrator as subject. Reloadable and config-cascade aware down to the context. Defaults to false, so an enabled OAuth provider does not open the endpoint unnoticed. No operator action is required by default.

SCR-1902

Summary: New properties com.openexchange.scim.deleteMode, com.openexchange.scim.allowBasicAuth and com.openexchange.scim.allowUnauthenticatedDiscovery

Effective: 8.54.320 and later

Three lean configuration properties control the new SCIM service provider. All three are reloadable and config-cascade aware down to the context.

  • com.openexchange.scim.deleteMode defines what DELETE /scim/v2/contexts/{context_id}/Users/{id} does: deactivate keeps the account and its data and only refuses login, delete removes the account and its data the way the provisioning API does. It defaults to deactivate, so a misconfigured identity provider cannot wipe a context in one sync cycle.
  • com.openexchange.scim.allowBasicAuth defines whether the SCIM endpoint accepts HTTP basic credentials of administrators next to provisioning tokens. It defaults to true; false makes the endpoint token-only, which exposes no password to online guessing.
  • com.openexchange.scim.allowUnauthenticatedDiscovery defines whether the discovery endpoints /ServiceProviderConfig, /ResourceTypes and /Schemas answer a caller that presents no credential. They describe the protocol, not the context, and identity providers read them before they authenticate. It defaults to true; a credential that is presented is checked either way, and false requires one for discovery as well.

No operator action is required by default.

SCR-1900

Summary: New lean configuration property com.openexchange.push.dovecot.unregisterOnMissingSession

Effective: 8.54.320 and later

In order to keep Dovecot Push registrations from piling up for users whose session has ended, the new lean configuration property com.openexchange.push.dovecot.unregisterOnMissingSession is introduced. When Dovecot notifies about a new message for a user for which no session can be resolved, the middleware drops that user's push registration through DoveAdm. It defaults to true, is reloadable and not config-cascade aware.

The registration is kept if it may still be wanted, that is if a permanent push listener or push notification subscriptions exist for that user. A configured DoveAdm end-point (com.openexchange.dovecot.doveadm.endpoints) is required; without one no registration is dropped. Set the property to false to restore the previous behavior.

See the property documentation for further details.

SCR-1892

Summary: New configuration option for caching the master administrator's password verification on provisioning calls

Effective: 8.54.320 and later

Provisioning calls are session-less and verify the master administrator's password against mpasswd on every call; with the default BCRYPT hash that costs about 100 ms of CPU per call and caps the master-level throughput. A successful verification is now remembered per node for a configurable time, so that further calls with the same credentials skip the password hash. Only a keyed hash of the password under a random per-node key is kept in memory; a wrong password always pays the full check, and reloading mpasswd with a changed hash invalidates the remembered verification. In addition, the duration of administrator authentication is exposed as Micrometer timer appsuite.provisioning.auth.duration with tags mode (master or context) and status.

  • com.openexchange.admin.masterPasswordVerificationTtlSeconds Specifies how long (in seconds) a successful verification of the master administrator's password is remembered on a node. A value less than or equal to 0 (zero) disables the cache and verifies the password hash on every call. Default 300. Reloadable, not config-cascade aware. No dedicated properties file.

SCR-1884

Summary: New configuration options for remote Redis sites

Effective: 8.54.320 and later

Options for using remote Redis sites for cache invalidation, e.g. between two middleware clusters attached to one config database.

  • com.openexchange.redis.sites.scope What the configured remote sites are used for. all: sessions are replicated to the remote sites and looked up there, and cache invalidations are repeated there. invalidation: cache invalidations only; the sessiond sees no remote sites, so sessions stay on their site. Use invalidation for clusters that merely share the databases. Default all. Not reloadable, not config-cascade aware. File: redis.properties.

  • com.openexchange.redis.[site].cache.enabled Whether the remote site denoted by [site] (one of the identifiers listed in com.openexchange.redis.sites) runs a dedicated Redis instance for cache data. If enabled, that instance is configured through the [site].cache infix, e.g. com.openexchange.redis.[site].cache.hosts, and cache invalidations are repeated there; otherwise they are repeated on the remote site's regular Redis instance. Default false. Not reloadable, not config-cascade aware. File: redis.properties.

SCR-1879

Summary: New properties "vacation.internal.enabled" and "vacation.internal.externalTagHeader" for vacation notices treating internal and external senders differently

Effective: 8.54.320 and later

Two new lean configuration properties control the extended (internal/external) vacation rules of the accompanying HTTP-API SCR. Both are reloadable and config-cascade aware; the feature ships dark and requires an explicit operator opt-in:

  • com.openexchange.mail.filter.options.vacation.internal.enabled — the feature toggle, defaults to false.

  • com.openexchange.mail.filter.options.vacation.internal.externalTagHeader — the name of the classifier header the mail platform stamps on every delivered message (e.g. Vacation-External), defaults to empty; configuring it is a prerequisite. The values are fixed convention: true marks the sender external, false internal. A message carrying neither value — or both — receives no vacation notice at all: broken or missing stamping fails closed instead of leaking the internal text to external senders.

The mail platform MUST delete inbound instances of the classifier header before stamping, on every delivery path without exception. Before enabling, verify the stamping end to end following the recipe in the feature documentation: a message injected from an external sender with a forged header instance must arrive carrying the genuine external stamp instead — proving both deleting and stamping — on every delivery path (MX, internal delivery, forwards, list expansion). Do not enable the feature before every node in the cluster runs 8.54 or later, and only once a rollback below 8.54 is no longer expected: an older middleware keeps delivery working but edits extended rules unsafely, and any script write from a pre-8.54 node — saving or deleting any rule, not only vacation notices — temporarily splits an extended rule's branches in the middleware's rule model (delivery is unaffected; an 8.54 middleware reattaches the branches on the next read). The security contract, a stamping recipe, and the operator runbook including the downgrade behavior are described in the feature documentation; see the property documentation for the full property descriptions.

SCR-1877

Summary: New configuration option for contact auto-complete result limiting

Effective: 8.54.320 and later

In order to bound the size of a contact auto-complete response, a new lean configuration property is introduced for the contacts module.

  • com.openexchange.contact.autocomplete.maxResults Defines the maximum number of contacts returned by an addressbooks?action=autocomplete request that does not supply a right_hand_limit of its own. Such a request previously returned every matching contact, so a query of one or two characters could read and transfer a large part of the address book. A value of 0 (zero) or less disables the limit. Default 100. Reloadable, config-cascade aware. File: contact.properties.

Related change in the same area: a client-supplied right_hand_limit is now applied to auto-complete requests for every sort order. It was previously discarded whenever sort was omitted or set to one of the special sort orders, which are the ones App Suite uses for contacts.

SCR-1876

Summary: New configuration option for the virtual CalDAV collection "All my public appointments"

Effective: 8.54.320 and later

A new configuration option enables the virtual CalDAV collection All my public appointments, which exposes all appointments the user attends in public calendars as an additional calendar, without having to synchronize whole public calendars. Appointments can neither be created nor deleted through the collection, but the own participation status and alarms of existing appointments can be changed. The collection is only offered to users with access to public folders.

  • com.openexchange.caldav.allPublicEvents.enabled Enables the virtual All my public appointments collection via CalDAV. Default false. Reloadable, config-cascade aware. File: caldav.properties.

SCR-1868

Summary: Changed the custom fields syntax in logback.xml

Effective: 8.54.320 and later

Custom fields for the JSON and Logstash encoders are now declared with nested <name> and <value> elements inside <customField>. The previous attribute form, together with the <newRule> declaration for com.openexchange.logback.extensions.encoders.CustomFieldAction, silently stopped working when logback removed support for <newRule> in version 1.3, so no custom field has been written since. Administrators using custom fields have to rewrite those entries in logback.xml and can drop the <newRule> line; an incomplete field is now reported as a warning on the appender.

<appender name="LOGSTASH" class="com.openexchange.logback.extensions.appenders.logstash.LogstashAppender">
    <encoder class="com.openexchange.logback.extensions.encoders.JSONEncoder">
        <customField><name>deployment</name><value>production</value></customField>
        <customField><name>cluster</name><value>eu-1</value></customField>
    </encoder>
</appender>

SCR-1862

Summary: Renamed misspelled property com.openexchange.mail.prependReplyPrefx to com.openexchange.mail.prependReplyPrefix

Effective: 8.54.320 and later

The property com.openexchange.mail.prependReplyPrefx - which controls whether the reply prefix (e.g. Re: ) is prepended to the unquoted reply text instead of being included in the quoted text - was misspelled. It is now named com.openexchange.mail.prependReplyPrefix. It defaults to false and is neither reloadable nor config-cascade aware.

No operator action is required: the misspelled name is still evaluated as a fallback, so an existing configuration keeps working. It is deprecated and logs a warning once when it takes effect; deployments that set it should move to the corrected name.

See the property documentation for further details.

SCR-1857

Summary: New properties for the OpenCloud file storage

Effective: 8.54.320 and later

The following properties are introduced for the file storage service opencloud. All of them are reloadable, and all but com.openexchange.file.storage.opencloud.entityResolver.attribute and com.openexchange.file.storage.opencloud.entityResolver.mappingFile are config-cascade aware, so a deployment may integrate a different OpenCloud instance per context or user.

  • com.openexchange.capability.filestorage_opencloud enables the storage for a user. It defaults to false.
  • com.openexchange.file.storage.opencloud.entityResolver.attribute states the attribute the users of an instance are matched with the ones of the server by, either mail or username. It defaults to mail.
  • com.openexchange.file.storage.opencloud.entityResolver.mappingFile states the path to a file mapping the users and groups of an instance to the ones of the server statically, which takes precedence over matching them by an attribute. It is empty by default.
  • com.openexchange.file.storage.opencloud.entityResolver.folderPermissions states whether the shares of a folder are expressed as its permissions, which requires a request per folder. It defaults to false.
  • com.openexchange.file.storage.opencloud.entityResolver.objectPermissions states whether the shares of a file are expressed as its object permissions. It defaults to false.
  • com.openexchange.file.storage.opencloud.entityResolver.createdModifiedBy states whether the users that created and modified an item are looked up within the instance. It defaults to true, and states the session's user otherwise.
  • com.openexchange.oauth.opencloud.hostname states the host of the instance the deployment integrates with. It is empty by default and required if com.openexchange.oauth.opencloud.enabled is set to true.
  • com.openexchange.oauth.opencloud.authorizationUrl states the URL the user is directed to in order to authorize the deployment. It is empty by default, which uses the endpoint of the identity provider built into OpenCloud.
  • com.openexchange.oauth.opencloud.tokenUrl states the URL an authorization code and a refresh token are redeemed at. It is empty by default, which uses the endpoint of the identity provider built into OpenCloud.
  • com.openexchange.oauth.opencloud.userInfoUrl states the URL the identity of the user is read from. It is empty by default, which uses the endpoint of the identity provider built into OpenCloud.
  • com.openexchange.oauth.opencloud.scope states the scopes that are requested when the deployment is authorized, separated by spaces. It defaults to openid profile email offline_access.

OAuth is configured through the properties of an OAuth service with the service identifier opencloud, so com.openexchange.oauth.opencloud.enabled, apiKey, apiSecret, redirectUrl and productName. An instance that is fronted by an identity provider of its own, e.g. Keycloak, states its endpoints through the properties above. A public client that authenticates with PKCE states no apiSecret. Such an instance associates a token with one of its users by the claims the token states, e.g. the roles or the groups of that user, so the client the deployment is authorized with needs to state the same claims as the client of the instance itself, which may require a further scope.

See the property documentation for further details.

SCR-1854

Summary: New property com.openexchange.calendar.useNoReplyAddressForNotifications

Effective: 8.54.320 and later

In order to let deployments whose no-reply relay is not authorized for the users' mail domains pass SPF and DMARC checks, the new lean configuration property com.openexchange.calendar.useNoReplyAddressForNotifications is introduced. If enabled, the configured no-reply address replaces the From header of calendar notification mails to internal recipients that are transported via the no-reply account, and the Sender and Reply-To headers are dropped. It defaults to false, is reloadable and config-cascade aware.

External iMIP messages are never affected, as their From header has to stay aligned with the ORGANIZER property. Note that the property keys on the recipient being internal, not on the message kind: with com.openexchange.calendar.useIMipForInternalUsers enabled, internal users receive full iMIP messages and those are rewritten as well, which RFC-conformant calendar clients may reject. Enable both only if internal recipients read their invitations in App Suite.

It applies wherever the no-reply account is used, which is not limited to com.openexchange.calendar.preferNoReplyForNotifications: guests, users without webmail permission, restricted sessions and impersonation sessions take that account on their own, so mails triggered by them change as well. The value is evaluated for the acting user, not for the organizer. No operator action is required by default. See the property documentation for further details.

Database

SCR-1987

Summary: Added the "xctx_grants" Cross-Context Grant Index Table and Create-Table Update Task

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.crosscontext.impl.storage.rdb.groupware.CrossContextGrantsCreateTableTask}}

New per-context table xctx_grants in the resource owner's context user schema - the mirror of xctx_liaisons, one small row per foreign context holding access on a resource of this context.

CREATE TABLE xctx_grants (
  `cid` INT4 UNSIGNED NOT NULL,
  `entity` INT4 UNSIGNED NOT NULL,
  `module` INT4 UNSIGNED NOT NULL DEFAULT 0,
  `grantee_cid` INT4 UNSIGNED NOT NULL,
  `type` INT4 UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`cid`, `entity`, `module`, `grantee_cid`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

cid/entity = owner context + resource owner; grantee_cid = the foreign context holding access; type = LiaisonType (3 = SHARED_ACCOUNT). A shared account permission is stored in the grantee's context, so the account's own context has no record of it - this index is what makes it answerable.

  • Fresh schemas: com.openexchange.crosscontext.impl.storage.rdb.groupware.CrossContextGrantsCreateTableService
  • Existing schemas: com.openexchange.crosscontext.impl.storage.rdb.groupware.CrossContextGrantsCreateTableTask (UpdateTaskAdapter, no dependencies, idempotent)
  • Cleanup and backfill: CrossContextGrantsDeleteListener plus the DatabaseCleanUpService jobs SharedAccountGrantsSourceCleanUpExecution and GrantsCleanUpExecution (1/day each)

The source-side job backfills grants issued before the index existed. It skips any schema with a pending update task, so on an upgraded system the index completes once those updates have run.

See the feature documentation for further details.

SCR-1952

Summary: New tables mail_credential and mail_credential_migration

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.mail.credential.impl.groupware.MailCredentialCreateTableTask}}

The two new tables mail_credential and mail_credential_migration in every context schema belong to the mail credential vault: the first keeps the encrypted mail credentials, the second how far the clean-up job that moves credentials of the old format into the vault has come. Both are added by the update task com.openexchange.mail.credential.impl.groupware.MailCredentialCreateTableTask. Expired rows of mail_credential are dropped hourly by a database clean-up job; rows of deleted users and contexts are dropped with them. A table whose credentials have all been moved carries a non-zero finished in mail_credential_migration. No admin action.

Table layout:

CREATE TABLE mail_credential (
  `cid` INT4 UNSIGNED NOT NULL,
  `id` BINARY(16) NOT NULL,
  `user` INT4 UNSIGNED NOT NULL,
  `feature` VARCHAR(64) NOT NULL,
  `kind` VARCHAR(16) NOT NULL,
  `caller_key` TINYINT(1) NOT NULL DEFAULT 0,
  `login` VARCHAR(255) DEFAULT NULL,
  `ciphertext` TEXT DEFAULT NULL,
  `created` BIGINT(20) NOT NULL,
  `expires` BIGINT(20) NOT NULL DEFAULT 0,
  `version` INT4 NOT NULL DEFAULT 1,
  `lease` BIGINT(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`cid`,`id`),
  KEY `user` (`cid`,`user`,`feature`,`created`),
  KEY `expires` (`expires`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE mail_credential_migration (
  `table_name` VARCHAR(64) NOT NULL,
  `resume_at` BINARY(16) DEFAULT NULL,
  `finished` BIGINT(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`table_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

SCR-1947

Summary: New table access_token in the context database schema

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.accesstoken.impl.groupware.AccessTokenCreateTableTask}}

Personal access tokens are stored in the new table access_token of the context database schema, created for new schemas by the create-table service and for existing ones by the update task com.openexchange.accesstoken.impl.groupware.AccessTokenCreateTableTask: columns cid, id (BINARY(16)), user, hash (the SHA-256 hash of the secret; the secret itself is never stored), label, scopes, created, expires, last_used and credential (BINARY(16), the identifier of the row in mail_credential the mail credential vault keeps for the token, sealed with the token's secret; NULL when nothing is kept), with primary key (cid, id), unique key (cid, hash) and key (cid, user). Rows are removed together with their user or context; the vault row goes with the token when it is revoked or expires. No admin action beyond the usual update task run.

SCR-1922

Summary: New column softDeleted in the user tables

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.groupware.update.tasks.UserAddSoftDeletedColumnTask}}

The tables user and del_user of every context schema receive the nullable column softDeleted (BIGINT(64)), the time a user has been soft-deleted in milliseconds since the epoch, together with the index softDeletedIndex on user. New schemas receive the column with the table, existing schemas through the update task. Existing rows keep NULL, no data is migrated.

ALTER TABLE user ADD COLUMN softDeleted BIGINT(64) DEFAULT NULL;
ALTER TABLE user ADD INDEX softDeletedIndex (softDeleted);
ALTER TABLE del_user ADD COLUMN softDeleted BIGINT(64) DEFAULT NULL;

SCR-1903

Summary: New table scim_external_id in the context schema

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.scim.storage.internal.groupware.ScimStorageCreateTableTask}}

{{com.openexchange.scim.storage.internal.groupware.ScimExternalIdTextKeyTask}}

The SCIM service provider stores the externalId an identity provider assigns to a resource - a user, group, resource, shared account, deputy or secondary account - in the new table scim_external_id of every context schema, unique per context and resource type and compared case-sensitively. The identifier of the resource is kept as text, since deputies and secondary accounts have none that is a number. New schemas receive the table from ScimStorageCreateTableService, existing schemas through the update task ScimStorageCreateTableTask, and a schema that received the table with a numeric id before is converted by ScimExternalIdTextKeyTask; the bundle com.openexchange.scim.storage ships with open-xchange-core so that every node knows the table. It starts empty, no data is migrated, and rows are removed with their user or context.

CREATE TABLE scim_external_id (
  cid INT4 UNSIGNED NOT NULL,
  type TINYINT UNSIGNED NOT NULL,
  id VARCHAR(191) NOT NULL,
  external_id VARCHAR(255) NOT NULL,
  PRIMARY KEY (cid, type, id),
  UNIQUE KEY external_id (cid, type, external_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

SCR-1898

Summary: New tables provisioning_token in the context schema and crosscontext_provisioning_token in the configdb

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.provisioning.token.impl.groupware.ProvisioningTokenCreateTableTask}}

Provisioning tokens bound to a context are stored in the new table provisioning_token of every context schema. New schemas receive it from ProvisioningTokenCreateTableService, existing schemas through the update task. The table holds the SHA-256 hash of a secret together with label, scope, creator and the creation, expiration and last-use times. It starts empty, no data is migrated.

CREATE TABLE provisioning_token (
  cid INT4 UNSIGNED NOT NULL,
  id BINARY(16) NOT NULL,
  hash VARCHAR(64) NOT NULL,
  label VARCHAR(128) NOT NULL,
  scope VARCHAR(32) NOT NULL,
  created_by VARCHAR(191) NOT NULL,
  created BIGINT(20) NOT NULL,
  expires BIGINT(20) NOT NULL DEFAULT 0,
  last_used BIGINT(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (cid, id),
  UNIQUE KEY hash (cid, hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Cross-context tokens, which open several contexts at once, live in the configuration database: the token in the new table crosscontext_provisioning_token, one row per context it opens in crosscontext_provisioning_token_context. Both are created at start-up, each by its own change set of configdbChangeLog.xml - 8:crosscontext_provisioning_token:create and 8:crosscontext_provisioning_token_context:create - so no manual step is needed; they start empty as well. Deleting a context removes its rows from crosscontext_provisioning_token_context, and a token left with no context is deleted along with it.

CREATE TABLE crosscontext_provisioning_token (
  id BINARY(16) NOT NULL,
  hash VARCHAR(64) NOT NULL,
  label VARCHAR(128) NOT NULL,
  scope VARCHAR(32) NOT NULL,
  created_by VARCHAR(191) NOT NULL,
  created BIGINT(20) NOT NULL,
  expires BIGINT(20) NOT NULL DEFAULT 0,
  last_used BIGINT(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (id),
  UNIQUE KEY hash (hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE crosscontext_provisioning_token_context (
  token BINARY(16) NOT NULL,
  cid INT4 UNSIGNED NOT NULL,
  PRIMARY KEY (token, cid),
  KEY cid (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

SCR-1890

Summary: New Column classifiedAccess in Table deputy for the Calendar Deputy Right to See Confidential Appointments

Effective: 8.54.320 and later

Warning

Update Task{{com.openexchange.deputy.impl.groupware.DeputyStorageAddClassifiedAccessColumnTask}}

In order to persist the new right of a calendar deputy to see the granting user's confidential appointments with the deputy permission itself (rather than in folder metadata a folder administrator could tamper with), the table deputy gained the column classifiedAccess next to sendOnBehalfOf, holding the granted level as numerical identifier (0 for none, 1 for confidential).

ALTER TABLE deputy ADD COLUMN classifiedAccess TINYINT UNSIGNED NOT NULL DEFAULT 0;

The update task adds the column to existing schemas, fresh schemas get it through the create table service. Existing rows keep the default 0, so no deputy gains the right implicitly and no operator action is required. The column is read by the calendar only when a deputy encounters a classified appointment in a shared calendar folder carrying deputy permissions, through the deputy service's storage-only listing of the grants the folder owner made to the deputy.

SCR-1853

Summary: New table deputy_mail_acl_baseline holding the mail ACL baseline of a deputy permission

Effective: 8.54.320 and later

Warning

Update Task com.openexchange.deputy.provider.imap.groupware.DeputyMailAclBaselineCreateTableTask

In order to record a deputy permission's mail ACL baseline reliably, the mailboxes on which the deputy already held an ACL before the permission was granted are now kept in the new table deputy_mail_acl_baseline instead of in the granting user's INBOX metadata entry /shared/vendor/vendor.open-xchange/deputydir-<deputyId>.

The baseline is now recorded once, at the first grant. It was previously re-captured on every grant, so a mailbox outside the permission's folder list that had only received its ACL through the grant itself, typically a subfolder Dovecot inherits from INBOX, counted as pre-existing and kept the deputy's ACL when the permission was revoked.

CREATE TABLE deputy_mail_acl_baseline (
  cid INT4 UNSIGNED NOT NULL,
  uuid BINARY(16) NOT NULL,
  user INT4 UNSIGNED NOT NULL,
  account INT4 UNSIGNED NOT NULL DEFAULT 0,
  fullname VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  rights VARCHAR(32) CHARACTER SET latin1 NOT NULL DEFAULT '',
  PRIMARY KEY (cid, uuid, account, fullname),
  KEY userId (cid, user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Deputy permissions granted before this change are not migrated; their baseline is still read from the metadata entry, and both locations are cleared when the permission is revoked. The update task only creates the table and does not touch existing rows. No operator action is required.

Packaging/Bundles

SCR-1999

Summary: New bundle com.openexchange.mail.messageid.redis for the folder rename history of the Mobile API

Effective: 8.54.320 and later

The package open-xchange-core ships the new bundle com.openexchange.mail.messageid.redis. It keeps a 30-day history of mail folders renamed or moved through App Suite in Redis, one hash ox-mail-folder-renames:<context>:<user>:<account> per mail account, so that Mobile API clients can recognize renamed folders on mail servers whose folder identifiers depend on the name (not Dovecot). Entries are written only for users with com.openexchange.mobile.api.enabled; updates use a Lua script unless com.openexchange.redis.lua.enabled is false. No admin action.

SCR-1997

Summary: New package open-xchange-mobile-api and Helm feature mobile-api

Effective: 8.54.320 and later

The new package open-xchange-mobile-api ships the bundle com.openexchange.mobile.api, which serves the Mobile API at /mobile/v1. It requires open-xchange-core and open-xchange-oauth-provider.

The core-mw Helm chart keeps the package switched off through the new feature mobile-api, like mcp. Both switches are needed: the feature starts the bundle, com.openexchange.mobile.api.enabled lets it answer.

SCR-1979

Summary: New bundle com.openexchange.pns.transport.sse

Effective: 8.54.320 and later

Added the new bundle com.openexchange.pns.transport.sse to the open-xchange-pns-impl package. It provides the push notification transport sse, which delivers notifications to live change streams (Server-Sent Events) and shares the presence of open streams among the nodes via Redis. The transport is disabled by default, see com.openexchange.pns.transport.sse.enabled.

SCR-1953

Summary: New bundles for WebDAV-Push: com.openexchange.dav.push and com.openexchange.pns.transport.webpush

Effective: 8.54.320 and later

The package open-xchange-pns-impl gains the bundle com.openexchange.pns.transport.webpush: a push notification service transport with id webpush that delivers RFC 8291 encrypted Web Push messages with RFC 8292 VAPID authorization to arbitrary push services (UnifiedPush distributors, FCM Web Push endpoints). It exports com.openexchange.pns.transport.webpush.WebPushService and the packages ...webpush.crypto and ...webpush.vapid, registers the managed HTTP client webpush, and imports com.openexchange.keystore from open-xchange-core to read the VAPID key from a Kubernetes secret. Public key derivation uses the BouncyCastle bundles already on the platform; no new third-party library.

The package open-xchange-dav gains the bundle com.openexchange.dav.push: WebDAV-Push (draft-bitfire-webdav-push-00, as used by DAVx5) for CalDAV and CardDAV. It contributes the transports, topic and supported-triggers property mixins, the push-register handling, the registration servlet below /push, the calendar, contact and task handlers that turn changes into notifications for the client webdav-push, and the push-message generator. It plugs into the DAV stack via the new SPI com.openexchange.dav.DAVPushService exported by com.openexchange.dav, which additionally gained the webdav-push DAV header option, the POST hook and the Push-Dont-Notify header handling. Because the bundle uses the transport's service, open-xchange-dav now depends on open-xchange-pns-impl.

Both bundles start with their packages and stay inert until configured: no VAPID key means no transport, and com.openexchange.dav.push.enabled defaults to false. No Helm chart change (the secret uses the existing OX_KEYSTORE mechanism), no database change (regular push notification service subscriptions), no admin action unless push for DAVx5 is wanted.

See the general documentation for further details.

SCR-1949

Summary: New package open-xchange-mcp with the bundles of the MCP server

Effective: 8.54.320 and later

The new package open-xchange-mcp contains the bundles com.openexchange.mcp (MCP endpoint, prompts, me_get), com.openexchange.mcp.contacts (contacts_search, contact_get, users_search), com.openexchange.mcp.mail (mail_search, mail_get, mail_attachment_get, mail resources), com.openexchange.mcp.calendar (calendar_list_events, calendar_get_event, calendar_free_busy, resources_search), com.openexchange.mcp.files (files_search, file_get, file resources), com.openexchange.mcp.tasks (tasks_search, task_get), com.openexchange.mcp.vacation (vacation_get) and com.openexchange.mcp.reminders (reminders_list); each module bundle also registers its folder tool. com.openexchange.mcp exports the service interfaces com.openexchange.mcp.tools.McpTool and com.openexchange.mcp.resources.McpResource, so further bundles can contribute tools and resources. The package depends on open-xchange-core, which ships the personal access tokens the endpoint accepts (SCR-1946). The Helm chart core-mw knows it as the feature mcp (features.definitions.mcp), which is disabled by default in features.status, so the bundles are not started unless an operator enables the feature; the endpoint additionally needs com.openexchange.mcp.enabled=true. No admin action unless the MCP server is wanted.

SCR-1916

Summary: New package open-xchange-admin-soap-common required by the administrative SOAP packages

Effective: 8.54.320 and later

The administrative SOAP bundles each carried their own copy of the same XML types. Those shared types now live in the new bundle com.openexchange.admin.soap.common, shipped in the new package open-xchange-admin-soap-common.

The following packages now depend on it, so it is pulled in automatically on upgrade and no operator action is required:

  • open-xchange-admin-soap
  • open-xchange-admin-soap-reseller
  • open-xchange-admin-soap-usercopy

No published WSDL changes: every copy already used the same XML namespace and the same content model, so the consolidation is not visible on the wire. Existing SOAP clients are unaffected.

SCR-1904

Summary: Added package open-xchange-scim and the chart feature scim

Effective: 8.54.320 and later

The SCIM service provider is packaged as follows.

  • New package open-xchange-scim with the bundle com.openexchange.scim; it depends on open-xchange-core and open-xchange-admin.
  • New bundle com.openexchange.scim.storage in open-xchange-core, carrying the table, its update task and the delete listener.
  • New chart feature scim in helm/core-mw (features.definitions.scim = open-xchange-scim), disabled globally and enabled on the admin role by default through roles.admin.values.features.status.scim, so the endpoint runs on the admin pods only. Set it to disabled there to switch the endpoint off.

SCR-1855

Summary: New bundles for OpenCloud and Nextcloud and new dependency of open-xchange-file-storage-webdav on open-xchange-oauth

Effective: 8.54.320 and later

The file storage service opencloud and refactoring of existing file storage service nextcloud ships with two new bundles.

  • com.openexchange.opencloud, the generated client of the libre graph API, within the package open-xchange-file-storage-webdav
  • com.openexchange.oauth.opencloud, the OAuth provider of an OpenCloud instance, within the package open-xchange-oauth
  • com.openexchange.oauth.nextcloud, the OAuth provider of an Nextcloud instance, within the package open-xchange-oauth

The OpenCloud and Nextcloud storages require the package open-xchange-oauth to be enabled, no matter whether an account authenticates with a username and an app token or through OAuth, as they are not registered at all otherwise.

Prev
Important Changes