Add collaborative editing channels for wiki pages

What does this MR do and why?

Adds the ActionCable channel that relays Yjs updates between clients editing the same wiki page, plus the wiki_collaborative_editing flag definition that gates it.

The server is not a CRDT peer. It authorises the subscriber, appends opaque base64 updates to the Redis log and rebroadcasts them; all merging happens in the browser. An earlier attempt used Rust bindings for a Ruby-side CRDT and proved unsustainable to maintain, so CRDT logic stays out of Ruby.

Nothing here is reachable by a user. There is no client-side provider yet and no wiring into the wiki form, so the channel exists but nothing subscribes to it. Nothing to screenshot.

Subclass contract

BaseChannel resolves in this order: find_container, feature_enabled?(container), authorized?(container), find_document(container), document_key(document). Another editable surface should mean a new subclass, not changes to the base class.

Two decisions worth calling out, both driven by the ordering:

  • The flag is scoped to container.root_ancestor, not the user. A session is shared, so a per-user actor would let one participant edit through the CRDT while another edited the same page through the plain form, and the two would silently overwrite each other.
  • Authorisation runs on the container, before the page lookup. A wiki page is a Git blob, so resolving one costs a Gitaly call, and an unauthorised subscriber must not be able to trigger that by guessing paths.

Security properties

AppSec review was scoped to this slice.

  • Subscription is rejected unless the flag is on for the container root ancestor, the container has a wiki, the user has :create_wiki on it, and the page exists.
  • Granular token scopes are checked explicitly via granular_authorization_denied?, matching Noteable::NotesChannel. AccessTokenValidationService skips the legacy scope check for granular tokens, and Ability.allowed? reads role membership rather than token scopes, so without this a granular PAT lacking create_wiki could open a session. This was not in the original unsplit MR.
  • Display identity is stamped from current_user on every awareness relay, never read from the payload. The payload is opaque binary the server cannot inspect, so a name or avatarUrl carried inside it would be whatever the sender chose — the latter being an IP and timing beacon every peer's browser would load.
  • Payloads are capped at 1 MB (MAX_PAYLOAD_BYTES); updates are rate limited to 600/min via Gitlab::ApplicationRateLimiter, keyed in Redis on [current_user, document_key] so extra subscriptions to the same document cannot multiply the allowance.
  • Non-string payloads and unknown message types are ignored.

Wire protocol

request_snapshot carries a compaction token which the client must echo back on its snapshot message; document_full tells a client the log is at its ceiling. Both were added to DocumentStore in !253421 (merged) and are consumed here. ActionCableProvider needs updating for both when it lands in the next slice.

The durable copy of a page is still the Git commit written on save. The Redis log is ephemeral, so a late joiner can catch up and a session survives every client briefly disconnecting.

Part of a split

Slice 3 of 5, splitting !250607 (closed) as requested at !250607 (comment 3708895581). The original list skipped a 3, so this is the channels/AppSec item numbered 4 there.

  • Slice 1, merged: !251691 (merged)
  • Slice 2, merged: !252572 (merged)
  • Store hardening, merged: !253421 (merged)
  • Slice 4: ActionCableProvider, the client-side provider
  • Slice 5: wiring — wiki_form.vue, CollaboratorsIndicator, and making the feature reachable

Testing

44 examples across the two channel specs. RuboCop clean, bundle exec rake gitlab:permissions:validate passes.

References

How to set up and validate locally

Nothing to validate through the UI — no client subscribes to the channel yet.

bundle exec rspec spec/channels/collaborative_editing/

The specs cover the subscription guards (flag actor, permissions, granular tokens, missing page or container), identity stamping against a spoofed payload, the payload and rate limits, and the compaction and document-full paths.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Lee Tickett

Merge request reports

Loading
Loading