Commit graph

3140 commits

Author SHA1 Message Date
cursor[bot]
508f1551e3
Fix flaky TestScrubPost (#36686)
The should_preserve_non-content_fields subtest asserted UpdateAt was
unchanged after scrubPost, but scrubPost refreshes UpdateAt via
model.GetMillis(). When the scrub runs in a later millisecond than the
fixture setup, the strict equality fails intermittently.

Align the assertion with scrubPost behavior (UpdateAt >= pre-scrub value).

Tests-only change. Verified with `go test -run '^TestScrubPost$' -race
-count=100` and 10000 runs of the subtest locally.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-05-25 09:13:25 +05:30
Jesse Hallam
44ba06ee3c
MM-68248: Handle missing OpenSearch indexes gracefully before reindex (#36712)
* MM-68248: Handle missing indexes gracefully before reindex

OpenSearch v3 rejects _update_by_query and _delete_by_query with no index
argument (405), and returns index_not_found_exception (404) when querying
an exact index name that hasn't been created yet. Both arise before any
reindex has run, since indexes are created on first document write.

Return nil/empty instead of an error from all affected operations, and add
test coverage for each in the no-indexes state.

* MM-68248: Fix copy-paste operation names in DeleteFilesBatch

* MM-68248: Add i18n string for delete_files_batch error

* MM-68248: Also check error type in isIndexNotFound
2026-05-22 16:05:03 +00:00
Jesse Hallam
834a86b5e3
MM-68248: Support OpenSearch v3 (#36617)
* Remove build-opensearch-image.yml -- the published image is unused

* MM-68248: Support OpenSearch v3

* MM-68248: Add CI job to test OpenSearch v2 backwards compatibility

* MM-68248: Handle missing indexes gracefully before reindex

OpenSearch v3 rejects _update_by_query and _delete_by_query with no index
argument (405), and returns index_not_found_exception (404) when querying
an exact index name that hasn't been created yet. Both arise before any
reindex has run, since indexes are created on first document write.

Return nil/empty instead of an error from all affected operations, and add
test coverage for each in the no-indexes state.

* MM-68248: Fix copy-paste operation names in DeleteFilesBatch

* MM-68248: Add i18n string for delete_files_batch error

* Revert "MM-68248: Add i18n string for delete_files_batch error"

This reverts commit e885678088.

* Revert "MM-68248: Fix copy-paste operation names in DeleteFilesBatch"

This reverts commit 4b7caacf59.

* Revert "MM-68248: Handle missing indexes gracefully before reindex"

This reverts commit 2d2d522f86.
2026-05-22 15:37:50 +00:00
David Krauser
e8632bd456
[MM-68777] Add admin property field permission level (#36558) 2026-05-22 11:01:41 -04:00
cursor[bot]
4e01dae534
Fix flaky TestPreparePostForClient/files (#36631)
The files subtest polled for post file metadata with assert.Eventually
using only a one-second total wait. Under CI load, PreparePostForClient
can take longer than that to see persisted file rows, causing intermittent
timeouts while the final assert would still pass if given time.

Tests-only change. Verified with go test -run
'^TestPreparePostForClient$/^files$' -race -count=100 locally.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>
2026-05-22 10:40:49 -04:00
Doug Lauder
c63154598c
MM-66162: harden GET /sharedchannels/{id}/remotes error path and add WS guard (#36696)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* MM-66162: harden GET /sharedchannels/{id}/remotes error path and add WS guard

  Two small follow-ups on top of the existing fix that returns 200 with an
  empty list when a channel is not shared.

  Server: getSharedChannelRemotes built its 500 AppError with nil params,
  so the i18n template "Could not fetch status for secure connections:
  {{.Error}}." rendered as "<no value>" in logs and response bodies when a
  genuinely unexpected error occurred. Pass map[string]any{"Error":
  err.Error()} so the underlying error surfaces, matching the pattern
  already used by the slash command caller in
  channels/app/slashcommands/command_share.go.

  Webapp: handleSharedChannelRemoteUpdatedEvent dispatched
  fetchChannelRemotes unconditionally on every shared_channel_remote_updated
  WS event, including for channels the local state already knows are not
  shared. The server publishes this event from the onInvite callback after
  a remote accepts a channel invitation, by which point the channel has
  already been flipped to shared and the channel_updated event that did so
  has already fired, so the local channel should reflect shared=true when
  this event arrives. If it does not, the event is not actionable and we
  skip the fetch.

  Adds jest coverage for the four cases of the new guard.

* add regression test for stale shared channel state

  Pins the DB inconsistency scenario suggested in the original ticket
  investigation: Channel.shared is true and a SharedChannelRemote row
  exists, but the SharedChannels row is missing. The endpoint should
  return 200 with an empty list rather than surfacing the stale state
  as an internal server error. Also tightens the existing
  TestGetSharedChannelRemotes by asserting the status code on the
  success path.
2026-05-22 09:59:15 -04:00
cursor[bot]
efdebec6ad
Fix flaky TestPatchTeam GroupConstrained subtest (#36689) 2026-05-22 08:38:27 -04:00
cursor[bot]
182049583b
Skip flaky TestGetLogs (MM-68910) (#36659)
* Skip flaky TestGetLogs

Skipping while the root cause is investigated under MM-68910.

Tests-only change.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* Address PR feedback: 2 items resolved, 0 declined

* Address PR feedback: 2 items resolved, 0 declined

* Address PR feedback: 2 items resolved, 0 declined

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-05-22 12:50:24 +02:00
Andre Vasconcelos
b4aa46a4be
Bumping version of prepackaged boards plugin (#36701)
Automatic Merge
2026-05-22 10:53:37 +02:00
Ben Schumacher
209606f15b
MM-68419: Add expires_at to PAT data model and enforce expiry at token validation (#36243)
* Add expires_at to PAT data model and enforce expiry at token validation

Adds an ExpiresAt field (int64 millis, 0 = never expires) to the
UserAccessToken model and DB table, enforces expiry when a PAT is used
to create a session, clamps the resulting session's ExpiresAt to the
token's expiry so cached sessions also honor it, ships a background job
(cleanup_expired_access_tokens) that periodically deletes expired
tokens along with any sessions minted from them, and emits audit events
for rejected and reaped expired tokens.

Refs: MM-68419

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix govet shadow warnings in DeleteExpired

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Stabilize expired PAT test by persisting an already-expired token

The previous variant created a live token, used it to mint a session,
then backdated the row and revoked the cached session to force a
re-validation. That flow was race-prone under parallel test execution
and was flagged as flaky in CI. Replace it with a direct store write
that persists the PAT with ExpiresAt already in the past, so
createSessionForUserAccessToken is exercised deterministically on the
first HTTP call and no session cache races are possible.

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Address PR review: batched cleanup, consistent filters, audit ordering

- server.go: initialize s.Audit before initJobs() so the cleanup worker
  never captures a nil audit logger.

- Replace DeleteExpired(cutoff) with DeleteByIds([]string) on
  UserAccessTokenStore. The worker now fetches a batch via
  GetExpiredBefore, emits one audit record per token, then deletes
  exactly that batch by id — guaranteeing 1:1 audit/delete pairing
  and eliminating the IsActive-filter mismatch between reads and
  deletes. The worker loops up to maxBatches (=1000) x batchLimit
  (=1000) rows per run and stops when GetExpiredBefore returns less
  than batchLimit or zero rows.

- GetExpiredBefore now selects an explicit column set that omits the
  secret Token column, so the PAT secret never travels from DB to app.

- DeleteByIds surfaces an error from RowsAffected instead of silently
  returning 0.

- Remove dead job.Data initialization in the worker.

- api4 test: set IsActive: true explicitly, walk the AppError chain
  and assert the specific Id app.user_access_token.expired so future
  401 regressions are caught.

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Use named returns in DeleteByIds and clean up expired fixture in test

- DeleteByIds now declares (deleted int64, err error) and uses bare
  returns on every error path so finalizeTransactionX can append a
  rollback failure to the returned error via merror.Append. Previously
  early returns short-circuited the deferred rollback's error
  contribution.
- Add the expired token to the test cleanup so all three fixtures are
  removed even on early test exit.

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Guard GetExpiredBefore against non-positive limit + tighten store test

- GetExpiredBefore now short-circuits when limit <= 0 and returns an
  empty slice without hitting the DB. This prevents the int -> uint64
  cast on a negative value from wrapping into an effectively unbounded
  query.
- Store test now asserts row.Token is empty for every row returned by
  GetExpiredBefore (not just the matched one) to catch any future
  query change that accidentally re-introduces the secret column.
- Added store-level coverage for the limit=0 and limit<0 short-circuit
  contract.

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Add session-clamping and worker tests; bump migration to 172

Address PR test-coverage analysis (issuecomment-4336010565):

- api4/user_test.go: add two subtests covering session.ExpiresAt
  behavior — clamped to token.ExpiresAt when the PAT has a non-zero
  ExpiresAt, and untouched (long-lived) when the PAT has no expiry.
- cleanup_expired_access_tokens/worker.go: extract the batching/audit/
  error orchestration into a package-private cleanupExpired() taking
  small interfaces (expiredTokenStore, auditRecorder) so it can be
  unit-tested without spinning up a job server.
- cleanup_expired_access_tokens/worker_test.go (new): seven unit tests
  cover happy path, empty result, full-batch -> next iteration,
  maxIter cap, GetExpiredBefore error propagation, DeleteByIds error
  propagation, and nil auditLogger guard.
- Bump migration 000170_add_expiresat_to_user_access_tokens to 000172
  to slot in behind the master-side 000170 (property_groups_version)
  and 000171 (drop_property_fields_protected_index).

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix PAT expiry audit ordering and cleanup scheduler gate

- Move IsExpired() check after EnableUserAccessTokens gate in
  createSessionForUserAccessToken so the AuditEventRejectExpiredUserAccessToken
  event only fires when PATs are active for the user, not when the feature
  is globally disabled.
- Tie the cleanup_expired_access_tokens scheduler to EnableUserAccessTokens
  so the hourly job does not schedule on servers where PATs are disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove per-token audit logging from expired PAT cleanup job

Background system jobs do not emit audit events in this codebase —
only user/admin-initiated actions do. The cleanup worker's per-token
AuditEventExpireUserAccessToken records were inconsistent with that
pattern (cleanup_desktop_tokens and other session jobs log nothing).

Also removes the early s.Audit init in NewServer that existed solely
to supply a non-nil logger to the worker.

The AuditEventRejectExpiredUserAccessToken event (emitted by
createSessionForUserAccessToken when a live request is rejected) is
unchanged — that is an auth gate firing in response to a request and
warrants auditing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Replace hand-rolled IN-clause helpers with squirrel query builder

Remove placeholders() and idsToArgs() from DeleteByIds — squirrel's
sq.Eq{"column": slice} generates the IN clause and argument list
automatically, matching the pattern used throughout the sqlstore package.

Also restructures the sessions delete from a PostgreSQL-specific
USING join to a portable subquery, keeping both statements expressible
via the query builder.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Drop redundant logger.Error calls in cleanup worker

SimpleWorker already logs any error returned from execute at the
Error level (base_workers.go:86). The extra logger.Error calls before
return were double-logging every failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Use mlog.CreateConsoleTestLogger in cleanup worker tests

Replaces the hand-rolled newTestLogger helper with the established
mlog.CreateConsoleTestLogger(t) pattern used by other job tests in
this package (jobs_test.go, recap/worker_test.go). It wires cleanup
and test-runner output automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Consolidate cleanup worker tests into subtests

Groups the six top-level TestCleanupExpiredXxx functions under a single
TestCleanupExpired parent with t.Run subtests. One shared logger is
created at the parent level; each subtest gets its own fakeStore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Revert incidental configureAudit restructure in server.go

The separation of s.Audit init from configureAudit was an unintended
side effect of an earlier commit. Restore the original pattern where
configureAudit is only called when s.Audit was nil at startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove api4 PAT expiry tests until API endpoint exists

The three tests (expired token rejected, session clamped, no-expiry
default) bypass the API to inject ExpiresAt via the store directly,
since no API endpoint exists yet to create tokens with an expiry.
They belong in the PR that adds that endpoint.

The same behaviors are covered at the appropriate layer by
storetest/user_access_token_store.go and model/user_access_token_test.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Prevent ExtendSessionExpiryIfNeeded from overriding PAT session expiry

PAT-authenticated sessions have their ExpiresAt clamped to the token's
ExpiresAt in createSessionForUserAccessToken. However, ExtendSessionExpiryIfNeeded
was resetting that expiry to now+SessionLengthWebInHours on the first
subsequent request, effectively bypassing PAT expiry for cached sessions.

Guard the extension to skip SessionTypeUserAccessToken sessions until
GetSessionLengthInMillis learns to return a length bounded by token.ExpiresAt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Strip ExpiresAt in create-token handler until API officially supports it

The JSON decoder populates the full accessToken struct from the request body,
and only UserId and Token were being overwritten before the store call. This
allowed clients to set an arbitrary expires_at (including 0 for non-expiring)
through the existing endpoint, contradicting the PR description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Clear session cache for affected users after DeleteByIds in cleanup worker

DeleteByIds removes sessions from the DB but did not invalidate the in-memory
session cache. This left stale sessions readable from cache until eviction,
inconsistent with the RevokeSession path.

Thread a clearSessionCache callback through MakeWorker and cleanupExpired.
After each successful batch delete, call it for each unique UserId in the
batch. The callback is deduplicated per batch to avoid redundant cache
invalidations when a user has multiple expired tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add partial index on useraccesstokens.expiresat

The cleanup job queries expiresat on every scheduled run (hourly). Without an
index this is a full sequential scan. Add a partial index WHERE expiresat > 0
to match the query's filter, keeping the index small since most tokens have no
expiry set. Mirrors the idx_sessions_expires_at pattern on the sessions table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Register JobTypeCleanupExpiredAccessTokens in job permission switches

Without entries in SessionHasPermissionToReadJob, SessionHasPermissionToCreateJob,
and SessionHasPermissionToManageJob, the job type falls through to (false, nil),
which API handlers treat as HTTP 400. This made the cleanup job invisible to
System Console and unmanageable via API (list, cancel, manual trigger all 400).

Add the job type to the PermissionManageJobs / PermissionReadJobs groups in
all three switches, matching how other internal jobs like JobTypeMigrations
are handled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Teach GetSessionLengthInMillis to honor PAT ExpiresAt

Replace the blunt guard in ExtendSessionExpiryIfNeeded with proper logic in
GetSessionLengthInMillis: for PAT sessions with a fixed ExpiresAt, return the
remaining lifetime instead of the configured web-session hours.

This means newExpiry = now + (ExpiresAt - now) = ExpiresAt, so extension never
pushes the session past the token's own expiry. The elapsed threshold
collapses to zero for such sessions, so no spurious DB writes occur either.

Non-expiring PAT sessions (ExpiresAt == 0) continue to use normal web-session
extension, which is correct behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Split expiresat index into separate non-transactional migration (000175)

CREATE INDEX CONCURRENTLY cannot run inside a transaction block. The morph
migration runner wraps each file in a transaction by default, causing the
combined migration to fail.

Split the index creation out of 000174 into a new 000175 migration file
with the -- morph:nontransactional directive, following the same pattern
used by 000135, 000155, 000173 and others. The 174 down migration no longer
needs to drop the index since 175 owns it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update Beginx call to renamed Begin (sqlx wrapper API change on master)

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:30:30 +02:00
Weblate (bot)
b0d2f83620
Translations update from Mattermost Weblate (#36695)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* Translated using Weblate (Lithuanian)

Currently translated at 62.6% (4601 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lt/

* Translated using Weblate (Lithuanian)

Currently translated at 62.6% (4601 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lt/

* Translated using Weblate (Estonian)

Currently translated at 0.4% (31 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/et/

* Translated using Weblate (Estonian)

Currently translated at 0.4% (31 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/et/

* Translated using Weblate (Danish)

Currently translated at 8.1% (266 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/da/

* Translated using Weblate (Danish)

Currently translated at 8.1% (266 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/da/

* Translated using Weblate (Italian)

Currently translated at 44.6% (3281 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/it/

* Translated using Weblate (Italian)

Currently translated at 44.6% (3281 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/it/

* Translated using Weblate (Belarusian)

Currently translated at 88.6% (6509 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/be/

* Translated using Weblate (Khmer (Central))

Currently translated at 0.6% (48 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/km/

* Translated using Weblate (Khmer (Central))

Currently translated at 0.6% (48 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/km/

* Translated using Weblate (Latin)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/la/

* Translated using Weblate (Latin)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/la/

* Translated using Weblate (Latin)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/la/

* Translated using Weblate (Persian)

Currently translated at 54.5% (4006 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fa/

* Translated using Weblate (Persian)

Currently translated at 54.5% (4006 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fa/

* Translated using Weblate (Portuguese)

Currently translated at 27.1% (1993 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pt/

* Translated using Weblate (Portuguese)

Currently translated at 27.1% (1993 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pt/

* Translated using Weblate (Albanian)

Currently translated at 3.1% (228 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sq/

* Translated using Weblate (Albanian)

Currently translated at 3.1% (228 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sq/

* Translated using Weblate (Catalan)

Currently translated at 33.3% (1089 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ca/

* Translated using Weblate (Lao)

Currently translated at 0.1% (1 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/lo/

* Translated using Weblate (Macedonian)

Currently translated at 1.7% (126 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/mk/

* Translated using Weblate (Macedonian)

Currently translated at 1.7% (126 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/mk/

* Translated using Weblate (Hungarian)

Currently translated at 62.9% (2058 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/hu/

* Translated using Weblate (Estonian)

Currently translated at 0.5% (19 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/et/

* Translated using Weblate (Georgian)

Currently translated at 7.1% (525 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ka/

* Translated using Weblate (Georgian)

Currently translated at 7.1% (525 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ka/

* Translated using Weblate (Georgian)

Currently translated at 7.1% (525 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ka/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 4.4% (147 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/nb_NO/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.4% (7304 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.4% (7304 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hans/

* Translated using Weblate (Nepali)

Currently translated at 0.1% (6 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ne/

* Translated using Weblate (Malayalam)

Currently translated at 3.7% (272 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ml/

* Translated using Weblate (Malayalam)

Currently translated at 3.7% (272 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ml/

* Translated using Weblate (Mongolian)

Currently translated at 0.6% (21 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/mn/

* Translated using Weblate (French)

Currently translated at 65.2% (4788 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fr/

* Translated using Weblate (French)

Currently translated at 65.2% (4788 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fr/

* Translated using Weblate (Swedish)

Currently translated at 83.6% (2733 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/sv/

* Translated using Weblate (Japanese)

Currently translated at 91.4% (6715 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ja/

* Translated using Weblate (Japanese)

Currently translated at 91.4% (6715 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ja/

* Translated using Weblate (Croatian)

Currently translated at 25.8% (1896 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hr/

* Translated using Weblate (Croatian)

Currently translated at 25.8% (1896 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hr/

* Translated using Weblate (Filipino)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fil/

* Translated using Weblate (Filipino)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fil/

* Translated using Weblate (Filipino)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fil/

* Translated using Weblate (Romanian)

Currently translated at 48.3% (3551 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ro/

* Translated using Weblate (Romanian)

Currently translated at 48.3% (3551 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ro/

* Translated using Weblate (Amharic)

Currently translated at 0.1% (4 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/am/

* Translated using Weblate (Amharic)

Currently translated at 0.1% (4 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/am/

* Translated using Weblate (Sinhala)

Currently translated at 0.3% (12 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/si/

* Translated using Weblate (French)

Currently translated at 65.1% (2129 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/fr/

* Translated using Weblate (Bengali)

Currently translated at 0.9% (70 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/bn/

* Translated using Weblate (Bengali)

Currently translated at 0.9% (70 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/bn/

* Translated using Weblate (Bengali)

Currently translated at 0.9% (70 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/bn/

* Translated using Weblate (Georgian)

Currently translated at 3.6% (120 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ka/

* Translated using Weblate (Georgian)

Currently translated at 3.6% (120 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ka/

* Translated using Weblate (Romanian)

Currently translated at 58.0% (1897 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ro/

* Translated using Weblate (Frisian)

Currently translated at 56.6% (4161 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fy/

* Translated using Weblate (Frisian)

Currently translated at 56.6% (4161 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fy/

* Translated using Weblate (Frisian)

Currently translated at 56.6% (4161 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fy/

* Translated using Weblate (Russian)

Currently translated at 80.7% (2638 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ru/

* Translated using Weblate (Kazakh)

Currently translated at 12.7% (938 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk/

* Translated using Weblate (Kazakh)

Currently translated at 12.7% (938 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk/

* Translated using Weblate (Kazakh)

Currently translated at 12.7% (938 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk/

* Translated using Weblate (English (Australia))

Currently translated at 88.0% (2875 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/en_AU/

* Translated using Weblate (English (Australia))

Currently translated at 88.0% (2875 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/en_AU/

* Translated using Weblate (Hindi)

Currently translated at 26.7% (1961 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hi/

* Translated using Weblate (Hindi)

Currently translated at 26.7% (1961 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hi/

* Translated using Weblate (Dutch)

Currently translated at 98.4% (7230 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nl/

* Translated using Weblate (Dutch)

Currently translated at 98.4% (7230 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nl/

* Translated using Weblate (Galician)

Currently translated at 19.8% (1458 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/gl/

* Translated using Weblate (Galician)

Currently translated at 19.8% (1458 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/gl/

* Translated using Weblate (Swedish)

Currently translated at 88.4% (6497 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sv/

* Translated using Weblate (Swedish)

Currently translated at 88.4% (6497 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sv/

* Translated using Weblate (Arabic)

Currently translated at 0.1% (7 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ar/

* Translated using Weblate (Arabic)

Currently translated at 0.1% (7 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ar/

* Translated using Weblate (Albanian)

Currently translated at 0.9% (32 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/sq/

* Translated using Weblate (Vietnamese)

Currently translated at 66.5% (2174 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/vi/

* Translated using Weblate (German)

Currently translated at 90.6% (2962 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/de/

* Translated using Weblate (German)

Currently translated at 90.6% (2962 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/de/

* Translated using Weblate (Latvian)

Currently translated at 0.3% (24 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lv/

* Translated using Weblate (Latvian)

Currently translated at 0.3% (24 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lv/

* Translated using Weblate (Latvian)

Currently translated at 0.3% (24 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lv/

* Translated using Weblate (Thai)

Currently translated at 1.0% (77 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/th/

* Translated using Weblate (Thai)

Currently translated at 1.0% (77 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/th/

* Translated using Weblate (English (Australia))

Currently translated at 94.2% (6922 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/en_AU/

* Translated using Weblate (Serbian)

Currently translated at 8.4% (275 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/sr/

* Translated using Weblate (Turkish)

Currently translated at 83.4% (2725 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/tr/

* Translated using Weblate (Turkish)

Currently translated at 83.4% (2725 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/tr/

* Translated using Weblate (Portuguese)

Currently translated at 6.3% (207 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/pt/

* Translated using Weblate (Greek)

Currently translated at 25.3% (829 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/el/

* Translated using Weblate (Gujarati)

Currently translated at 0.1% (9 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/gu/

* Translated using Weblate (Gujarati)

Currently translated at 0.1% (9 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/gu/

* Translated using Weblate (Arabic (Saudi Arabia))

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ar_SA/

* Translated using Weblate (Arabic (Saudi Arabia))

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ar_SA/

* Translated using Weblate (English (Pirate))

Currently translated at 0.1% (2 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 0.1% (2 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 0.1% (2 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/en@pirate/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 98.1% (3208 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 98.1% (3208 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hans/

* Translated using Weblate (Bulgarian)

Currently translated at 61.5% (2011 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/bg/

* Translated using Weblate (Bulgarian)

Currently translated at 61.5% (2011 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/bg/

* Translated using Weblate (Korean)

Currently translated at 79.5% (2600 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ko/

* Translated using Weblate (Korean)

Currently translated at 79.5% (2600 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ko/

* Translated using Weblate (Indonesian)

Currently translated at 27.0% (883 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/id/

* Translated using Weblate (Basque)

Currently translated at 1.3% (43 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/eu/

* Translated using Weblate (Slovenian)

Currently translated at 13.8% (1015 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sl/

* Translated using Weblate (Slovenian)

Currently translated at 13.8% (1015 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sl/

* Translated using Weblate (Arabic (Saudi Arabia))

Currently translated at 0.0% (0 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ar_SA/

* Translated using Weblate (Breton)

Currently translated at 0.1% (7 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/br/

* Translated using Weblate (Breton)

Currently translated at 0.1% (7 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/br/

* Translated using Weblate (Bulgarian)

Currently translated at 59.0% (4335 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/bg/

* Translated using Weblate (Bulgarian)

Currently translated at 59.0% (4335 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/bg/

* Translated using Weblate (Amharic)

Currently translated at 3.0% (101 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/am/

* Translated using Weblate (Thai)

Currently translated at 0.7% (23 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/th/

* Translated using Weblate (Polish)

Currently translated at 99.4% (7304 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pl/

* Translated using Weblate (Polish)

Currently translated at 99.4% (7304 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pl/

* Translated using Weblate (Korean)

Currently translated at 84.1% (6179 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ko/

* Translated using Weblate (Korean)

Currently translated at 84.1% (6179 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ko/

* Translated using Weblate (Slovenian)

Currently translated at 33.4% (1092 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/sl/

* Translated using Weblate (Slovenian)

Currently translated at 33.4% (1092 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/sl/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 75.3% (5535 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hant/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 75.3% (5535 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/zh_Hant/

* Translated using Weblate (Finnish)

Currently translated at 38.2% (1249 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/fi/

* Translated using Weblate (Japanese)

Currently translated at 81.5% (2665 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ja/

* Translated using Weblate (Japanese)

Currently translated at 81.5% (2665 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ja/

* Translated using Weblate (Catalan)

Currently translated at 1.6% (121 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ca/

* Translated using Weblate (Catalan)

Currently translated at 1.6% (121 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ca/

* Translated using Weblate (Mongolian)

Currently translated at 1.6% (124 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/mn/

* Translated using Weblate (Mongolian)

Currently translated at 1.6% (124 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/mn/

* Translated using Weblate (Polish)

Currently translated at 96.6% (3159 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/pl/

* Translated using Weblate (Polish)

Currently translated at 96.6% (3159 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/pl/

* Translated using Weblate (Hebrew)

Currently translated at 0.6% (50 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/he/

* Translated using Weblate (Hebrew)

Currently translated at 0.6% (50 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/he/

* Translated using Weblate (Belarusian)

Currently translated at 85.6% (2799 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/be/

* Translated using Weblate (Belarusian)

Currently translated at 85.6% (2799 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/be/

* Translated using Weblate (Turkish)

Currently translated at 91.8% (6747 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/tr/

* Translated using Weblate (Turkish)

Currently translated at 91.8% (6747 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/tr/

* Translated using Weblate (Turkish)

Currently translated at 91.8% (6747 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/tr/

* Translated using Weblate (Italian)

Currently translated at 62.0% (2026 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/it/

* Translated using Weblate (Spanish)

Currently translated at 60.2% (4426 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/es/

* Translated using Weblate (Spanish)

Currently translated at 60.2% (4426 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/es/

* Translated using Weblate (Basque)

Currently translated at 0.1% (11 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/eu/

* Translated using Weblate (Basque)

Currently translated at 0.1% (11 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/eu/

* Translated using Weblate (Vietnamese)

Currently translated at 62.1% (4562 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/vi/

* Translated using Weblate (Vietnamese)

Currently translated at 62.1% (4562 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/vi/

* Translated using Weblate (Czech)

Currently translated at 82.2% (6037 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/cs/

* Translated using Weblate (Czech)

Currently translated at 82.2% (6037 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/cs/

* Translated using Weblate (Icelandic)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/is/

* Translated using Weblate (Icelandic)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/is/

* Translated using Weblate (Icelandic)

Currently translated at 0.0% (0 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/is/

* Translated using Weblate (Danish)

Currently translated at 8.9% (659 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/da/

* Translated using Weblate (Danish)

Currently translated at 8.9% (659 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/da/

* Translated using Weblate (Serbian)

Currently translated at 7.6% (559 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sr/

* Translated using Weblate (Serbian)

Currently translated at 7.6% (559 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/sr/

* Translated using Weblate (Breton)

Currently translated at 0.8% (29 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/br/

* Translated using Weblate (German)

Currently translated at 96.2% (7071 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/de/

* Translated using Weblate (German)

Currently translated at 96.2% (7071 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/de/

* Translated using Weblate (Indonesian)

Currently translated at 2.6% (191 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/id/

* Translated using Weblate (Indonesian)

Currently translated at 2.6% (191 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/id/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 73.3% (2396 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/zh_Hant/

* Translated using Weblate (Arabic)

Currently translated at 0.5% (18 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ar/

* Translated using Weblate (Kazakh (Latin script))

Currently translated at 3.2% (235 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk_Latn/

* Translated using Weblate (Kazakh (Latin script))

Currently translated at 3.2% (235 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk_Latn/

* Translated using Weblate (Kazakh (Latin script))

Currently translated at 3.2% (235 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/kk_Latn/

* Translated using Weblate (Gujarati)

Currently translated at 0.5% (19 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/gu/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.5% (2436 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/pt_BR/

* Translated using Weblate (Czech)

Currently translated at 81.7% (2670 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/cs/

* Translated using Weblate (Czech)

Currently translated at 81.7% (2670 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/cs/

* Translated using Weblate (Persian)

Currently translated at 60.0% (1963 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/fa/

* Translated using Weblate (Hungarian)

Currently translated at 59.6% (4383 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hu/

* Translated using Weblate (Hungarian)

Currently translated at 59.6% (4383 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hu/

* Translated using Weblate (Hungarian)

Currently translated at 59.6% (4383 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/hu/

* Translated using Weblate (Hebrew)

Currently translated at 1.1% (39 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/he/

* Translated using Weblate (Finnish)

Currently translated at 20.9% (1538 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fi/

* Translated using Weblate (Finnish)

Currently translated at 20.9% (1538 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/fi/

* Translated using Weblate (Croatian)

Currently translated at 8.0% (264 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/hr/

* Translated using Weblate (Sinhala)

Currently translated at 0.1% (10 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/si/

* Translated using Weblate (Sinhala)

Currently translated at 0.1% (10 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/si/

* Translated using Weblate (Greek)

Currently translated at 6.5% (482 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/el/

* Translated using Weblate (Greek)

Currently translated at 6.5% (482 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/el/

* Translated using Weblate (Malayalam)

Currently translated at 8.9% (292 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/ml/

* Translated using Weblate (Galician)

Currently translated at 1.7% (58 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/gl/

* Translated using Weblate (Ukrainian)

Currently translated at 80.4% (5905 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 80.4% (5905 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/uk/

* Translated using Weblate (Nepali)

Currently translated at 5.7% (424 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ne/

* Translated using Weblate (Nepali)

Currently translated at 5.7% (424 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ne/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 76.4% (5613 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nb_NO/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 76.4% (5613 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nb_NO/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 76.4% (5613 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/nb_NO/

* Translated using Weblate (Hindi)

Currently translated at 58.5% (1913 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/hi/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 62.0% (4557 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 62.0% (4557 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/pt_BR/

* Translated using Weblate (Khmer (Central))

Currently translated at 0.7% (26 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/km/

* Translated using Weblate (Lithuanian)

Currently translated at 6.7% (221 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/lt/

* Translated using Weblate (Ukrainian)

Currently translated at 78.5% (2567 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 78.5% (2567 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/uk/

* Translated using Weblate (Spanish)

Currently translated at 67.0% (2191 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/es/

* Translated using Weblate (Spanish)

Currently translated at 67.0% (2191 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/es/

* Translated using Weblate (Spanish)

Currently translated at 67.0% (2191 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/es/

* Translated using Weblate (Dutch)

Currently translated at 95.8% (3131 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/nl/

* Translated using Weblate (Macedonian)

Currently translated at 6.7% (221 of 3267 strings)

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/mk/

* Translated using Weblate (Lao)

Currently translated at 3.9% (290 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lo/

* Translated using Weblate (Lao)

Currently translated at 3.9% (290 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/lo/

* Translated using Weblate (Russian)

Currently translated at 76.7% (5636 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ru/

* Translated using Weblate (Russian)

Currently translated at 76.7% (5636 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ru/

* Translated using Weblate (Russian)

Currently translated at 76.7% (5636 of 7343 strings)

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/ru/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Mattermost/webapp
Translate-URL: https://translate.mattermost.com/projects/mattermost/webapp/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Mattermost/server
Translate-URL: https://translate.mattermost.com/projects/mattermost/server/

---------

Co-authored-by: jprusch <rs@schaeferbarthold.de>
Co-authored-by: Martin Popp Fredslund (SektorCERT) <martin@sektorcert.dk>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Ekaterine Papava <papava.e@gtu.ge>
Co-authored-by: Sharuru <mave@foxmail.com>
Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au>
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com>
Co-authored-by: master7 <marcin.karkosz@rajska.info>
Co-authored-by: DeepL <noreply-mt-deepl@weblate.org>
Co-authored-by: Takayuki Maruyama <bis5.wsys@gmail.com>
Co-authored-by: Vadim Asadchi <vadim.asadchi@codex-soft.com>
Co-authored-by: Martin Mičuda <micuda@rematiptop.cz>
Co-authored-by: ritchierope <ritchierope@users.noreply.translate.mattermost.com>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Serhii Khomiuk <sergiy.khomiuk@gmail.com>
Co-authored-by: Ricardo Obregón <robregonm@gmail.com>
Co-authored-by: Carloswaldo <waldosaurio@gmail.com>
Co-authored-by: Dmitriy Q <krotesk@mail.ru>
2026-05-22 01:47:43 +00:00
Nick Misasi
3570814ddd
MM-68316: add mattermost db ping subcommand (#36406)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* MM-68316: add `mattermost db ping` subcommand

Adds a one-shot DB-readiness CLI that opens a connection to the
configured PostgreSQL DSN and exits 0 when reachable. Honours
--timeout (default 5m) and --retry-interval (default 2s); reads the
DSN from --config / MM_CONFIG / MM_SQLSETTINGS_DATASOURCE matching
the existing `db init`/`db migrate` semantics.

Intended primarily as an init-container readiness check for the
mattermost-operator, removing its dependency on a separate
postgres:13 image (relevant for air-gapped deployments).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68316: address PR feedback (sanitize ping retry log)

Drop mlog.Err(err) from the per-attempt "Waiting for database" log line in
pingWithRetry: lib/pq error strings can echo DSN fragments back at operators
when DSN parsing fails. Replace with a non-sensitive status field. Also
clarifies the TestDBPingInvalidDSN comment to reflect actual lib/pq behavior
(parse error surfaces at PingContext, not Open, so the retry loop treats it
as transient — desired for a readiness probe).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68316 Preserve safe db ping DSN errors

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-05-21 18:46:52 +00:00
Christopher Poile
03f2eaaa0b
[MM-68400] Four plugin hooks and ChannelGuard enforcement (#36152)
* allow workflow_dispatch trigger for Server CI (for plugins CI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [MM-68402] MBE Phase 2: declare four generic plugin hooks (#36291)

* new hooks-only phase 2

* remove ChannelWillBeMoved

* remove RecapWillBeProcessed and MessageWillBeRewrittenByAI

Drop the AI/recap hooks from the new-hook surface; AI-LLM paths
remain uncovered in tech preview and are documented as residuals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68403] MBE Phase 3: ChannelGuards primitive (storage + cache + plugin API) (#36365)

* phase 3

* phase 3: register ChannelGuard mock in test setup helper

NewChannels' startup-time call to reloadGuardCache invokes
s.ChannelGuard().GetAll(); without an expectation on the mock store,
every test that sets up the server with GetMockStoreForSetupFunctions
panics during init.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* phase 3: register ChannelGuard mock in retrylayer test

retrylayer.New walks every store getter to wrap it; without the mock
expectation on ChannelGuard, TestRetry panics during layer construction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* use rctx properly in the store methods

* phase 3: match rctx arg in testlib ChannelGuard mock

GetAll now takes request.CTX, so the testify expectation must include
mock.Anything; otherwise the call panics under the mocked store.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* phase 3: set api.ctx in TestChannelGuardLowercaseNormalization

The test constructs PluginAPI directly without a ctx, which used to
work when App.RegisterChannelGuard built its own EmptyContext. Now
that the App methods take rctx from the caller, the nil ctx panics
inside RequestContextWithMaster.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68404] MBE Phase 4: App-layer plugin hook wiring (#36407)

* phase 4

* Fix nil rctx in TestChannelGuardLowercaseNormalization

The PluginAPI struct literal was missing ctx: rctx after a refactor
moved the rctx declaration below the struct construction, leaving
api.ctx as nil. This caused a nil pointer dereference in reloadGuardCache
when RegisterChannelGuard called store.RequestContextWithMaster(nil).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* Remove ChannelWillBeMoved hook call from MoveChannel (phase 4)

The hook and its ID were removed from mbe-phase-2 but the call site in
MoveChannel and its i18n string were not cleaned up during the rebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* remove channel will be moved test

* Remove RecapWillBeProcessed and MessageWillBeRewrittenByAI hook calls (phase 4)

The hooks and their IDs were removed from mbe-phase-2 but the call sites
in ProcessRecapChannel and RewriteMessage, their i18n strings, and their
tests were not cleaned up during the rebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert channel_id plumbing on rewrite endpoint (phase 4)

The channel_id field on RewriteRequest was added in phase 4 to feed the
synthetic post passed to MessageWillBeRewrittenByAI. With that hook
removed from mbe-phase-2, channel_id has no consumer; revert the field,
the api4 validation, the app.RewriteMessage parameter, and the
corresponding webapp client + hook plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68555] MBE Phase 5: Channel-guard enforcement + two-phase dispatch (#36473)

* phase 5

* Bake plugin counter-file paths into source instead of env vars

t.Setenv panics when an ancestor test calls t.Parallel, so the two
channel-guard tests broke under ENABLE_FULLY_PARALLEL_TESTS in CI.
Build each plugin source per-subtest with its temp file path embedded
as a Go literal — same pattern as TestPluginUploadsAPI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Remove guarded helpers and tests for dropped hooks (phase 5)

The runGuardedRecapWillBeProcessed and runGuardedMessageWillBeRewrittenByAI
helpers were never wired (their app-layer call sites were already removed
in the phase-4 cleanup), and the corresponding sub-tests across panic /
allow / reject / partial plugins reference hooks that no longer exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [MM-68405] MBE Phase 6: fire MessagesWillBeConsumed on the edit path (#36475)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* rebase onto master

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 18:16:05 +00:00
Jesse Hallam
b9e8d5ce82
MM-68763: fix BuildAccessControlSubject call missing channelID argument (#36681) 2026-05-21 19:18:30 +02:00
Ibrahim Serdar Acikgoz
e6c59693af
MM-68763: Discoverable Private Channels — Server feature complete (visibility, ABAC, queue API) (#36580) 2026-05-21 17:10:51 +02:00
Ibrahim Serdar Acikgoz
ba1cec51a5
[MM-68693] Resource level permission policies and new simulation (#36472) 2026-05-21 14:40:05 +02:00
Ben Schumacher
7739b349a0
[MM-68578] Add support packet DB performance diagnostics (#36324)
* Add support packet DB diagnostics for pool and pg_stat

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix support packet mock store for new DB diagnostics

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Add context timeouts to support packet pg diagnostics

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Move support packet DB diagnostics queries into sqlstore

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix support packet diagnostics lint and partial data handling

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Stabilize support packet pool idle assertion

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Relax live support packet DB counter assertions

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix deterministic pool diagnostics test wiring

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Mock support packet diagnostics in app test store

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Move SupportPacketDatabaseDiagnostics out of public model

The struct is an internal store→platform transport (no yaml tags, never
serialized directly) so it doesn't belong in server/public/model where
it would form a public API contract for plugins. Move it into the store
package as the natural return type of GetSupportPacketDatabaseDiagnostics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Describe SupportPacketDatabaseDiagnostics by content, not history

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* wording

* Align store diagnostics with sqlstore conventions

- Rename Store.GetSupportPacketDatabaseDiagnostics to Store.GetDiagnostics
  and rename the holding files to diagnostics{,_test}.go.
- Drop the queryRowScanner / rowScanner / sqlQueryRowScanner test seam.
  The collectors now use the sqlxDBWrapper master handle and bind result
  rows into local structs via sqlx GetContext, matching how the rest of
  the sqlstore package talks to Postgres.
- Replace the hand-rolled mock-based unit tests for the Postgres
  collector with an integration test driven through StoreTest, the
  pattern used by the other sqlstore tests (e.g. schema_dump_test.go).
  The pure pool-stats unit test (TestApplyDBPoolStats) is kept.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop MasterDBStats/ReplicaDBStats from the Store interface

After GetDiagnostics moved into the store layer, no caller outside the
sqlstore package itself reads MasterDBStats/ReplicaDBStats through the
Store interface — the diagnostics collector calls them on the concrete
*SqlStore receiver. Remove them from the interface, the retry/timer
layer wrappers, the storetest fake, and the generated mock; drop the
now-redundant fixedDBStatsStore shim methods and mock setups in the
support packet tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rename SupportPacketDatabaseDiagnostics to DatabaseDiagnostics

Now that the type lives in the store package and is returned by
Store.GetDiagnostics, the SupportPacket prefix is just legacy framing —
support packets are one consumer of the data, not its identity. Rename
to store.DatabaseDiagnostics for consistency with the package and method
name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Inline diagnostics SQL into the collector functions

Each pg_stat query has a single caller, so a package-level constant just
adds indirection between the function and the SQL it owns. Move the
query strings to local consts inside the collectors that use them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix Connectios typo to Connections

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix sqlstore diagnostics build: call GetMaster().DB() method

The recent rebase brought in a change that turned the SqlStore DB
field into a method, so passing ss.GetMaster().DB to
collectPostgresDatabaseDiagnostics (which expects *sqlx.DB) no longer
compiles. Call the method instead.

* Fix gofmt alignment in SupportPacketDiagnostics

The post-merge struct had extra spaces on MasterConnections /
ReplicaConnections that broke gofmt alignment.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:45:18 +02:00
Ben Schumacher
2ab07701b5
[MM-68577] Add OAuth2/OpenID Connect provider status to support packet (#36451)
* [MM-68577] Add OAuth2/OpenID Connect provider status to support packet

Probe configured GitLab, Google, Office365, and OpenID providers and report
their connectivity status in the support packet diagnostics. For providers
with a DiscoveryEndpoint, the probe verifies a valid OIDC discovery document
(JSON with an "issuer" field) is returned; otherwise it probes the
TokenEndpoint host, treating any HTTP response as reachable since token
endpoints reject GETs.

Disabled providers report status: disabled, enabled providers report ok or
fail with the underlying error. No secrets are read or transmitted; only
public endpoint URLs are probed.

* [MM-68577] Drain response body in probeOAuthTokenEndpoint

Closing resp.Body without first reading it leaves unread bytes on the wire,
which prevents net/http from returning the underlying TCP connection to the
idle pool for keep-alive reuse. Drain with io.Copy + io.LimitReader (1MB
cap to bound a misbehaving server) and use defer for the close.

* [MM-68577] Extract drainAndCloseBody helper for HTTP probe responses

Three call sites in this file (probeOIDCDiscovery, probeOAuthTokenEndpoint,
testPushProxyConnection) now share the same drain-then-close idiom needed
to keep TCP connections eligible for keep-alive reuse. Replace the inline
copies with a single drainAndCloseBody helper that bounds the discard at
1 MiB to limit exposure to a misbehaving server.

Also fixes the same un-drained Close() bug in the pre-existing
testPushProxyConnection while we're here.

* Add comment explaining 1 MiB discovery response size cap

Addresses review feedback asking for clarity on the 1<<20 limit.
2026-05-21 09:09:20 +00:00
Ben Schumacher
a7ef484fee
[MM-68576] Add SAML connectivity status to support packet diagnostics (#36321)
* Add SAML connectivity status to support packet diagnostics

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Fix SAML diagnostics tests for config validation

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Add enterprise SAML diagnostics hook for support packet

Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>

* Cleanup

* Fix SAML support packet tests to use enterprise mock interface

Tests were expecting the platform layer to perform HTTP metadata URL
checks directly, but that logic belongs in the enterprise SAML
diagnostic implementation. Updated tests to install a mock enterprise
interface (matching the existing pattern in the override test) instead
of relying on bare HTTP calls that only work without the enterprise
interface registered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Simplify SamlDiagnosticInterface to return error instead of (string, string)

The status return was always either StatusOk or StatusFail, which maps
directly to nil/non-nil error. Removing the redundant status string
makes the interface idiomatic Go and lets the call site derive status
from error presence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* lint fix

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ben Schumacher <hanzei@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:21:34 +02:00
cursor[bot]
981e5341ca
Fix flaky TestUserHasJoinedChannel (#36660)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* Fix flaky TestUserHasJoinedChannel

The UserHasJoinedChannel plugin hook is invoked from AddChannelMember via
Srv().Go, so the hook post can appear after the join system post. Under CI
load the 5s Eventually window was sometimes too short. Confirm the plugin
is active before triggering the hook, poll posts in channel order like the
sibling subtest, and extend the wait to 10s.

Tests-only change. Verified with `go test -run '^TestUserHasJoinedChannel$' -race
-count=50` locally.

Co-authored-by: mattermost-code <matty-code@mattermost.com>

* test: add plugin activation assertion messages

* test: deduplicate plugin hook post assertion

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: mattermost-code <matty-code@mattermost.com>
2026-05-20 22:19:54 +00:00
Jesse Hallam
2c925ccf88
MM-68151: Update server dependencies (#36571) 2026-05-20 18:20:19 -03:00
Scott Bishel
448a642835
Add inline action buttons for bot-posted markdown (#36219)
* Add inline action buttons for bot-posted markdown

Bots, webhooks, and plugins can now embed clickable action buttons
inside markdown (including table cells) using mmaction://actionId
links, with row-specific parameters forwarded to the integration on
click. This enables use cases like a per-row "Mx Plan" button in a
fleet-status table that opens a dialog scoped to the clicked row.

Design
- New post prop inline_actions maps actionId (alphanumeric) to a
  PostActionIntegration {URL, Context}, capped at 50 entries.
- Markdown link with scheme mmaction:// emits a placeholder span that
  messageHtmlToComponent converts to the InlineActionButton component.
- Click POSTs inline_context (parsed from the URL query string) to the
  existing /posts/{id}/actions/{action_id} endpoint; the server merges
  it into the integration request as context.inline_params while
  preserving the post-level context.
- Only bot, webhook, and plugin posts render the button; non-integration
  posts have inline_actions stripped on create, update, and ephemeral
  broadcast. Hardened-mode also covers the new prop.
- Reuses the existing PostAction dialog pipeline: plugin handlers reply
  with a trigger_id and call /actions/dialogs/open as before.

Security
- InlineContext capped at 50 entries / 128-char keys / 2 KB values.
- Integration Context cloned per click so per-click inline_params and
  selected_option cannot leak into the cached post for other clickers.
- Plugin response updates cannot add inline_actions to a post that did
  not already have them; invalid entries are dropped with a warn log.
- Label content and data attributes are escaped; labels are flattened
  to plain text (tags stripped, entities decoded, then escaped).
- Malformed JSON request bodies now return 400 instead of falling
  through with an empty inline_context.

Tests
- Model: validators, normalization, GetInlineAction, strip, fallback.
- App: create strip, update guard (4 subtests including
  AllowInlineActionsUpdate bypass), ephemeral strip, inline_params
  merge, context-map isolation, plugin-response guards, from_bot and
  from_plugin retention across plugin updates.
- API: inline_context validation (size bounds + error id),
  omitempty backward compat, malformed JSON 400.
- Webapp: renderer scheme handling, allow/deny flags, size caps,
  HTML escape, tag strip, entity decode, attribute-injection defense;
  component click dispatch, double-click race guard, unmount safety,
  error-result recovery, aria state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* lint fix

* i18n-extract

* Review fixes for inline action buttons

- renderer: preserve actionId case; reject opaque mmaction: URI
- app: require bot AND integration session to preserve inline_actions
- app: restore original inline_actions when plugin response is invalid
- i18n: rename key to ...app_error to match convention

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Tighten UpdatePost inline_actions guard; fix test seeds

- app: UpdatePost now requires AllowInlineActionsUpdate to modify
  inline_actions. Integration session alone is insufficient — a
  PAT-wielding user could otherwise inject inline_actions on any
  post they could edit.
- tests: seed bot posts with inline_actions via an integration
  session (intSeedCtx) so they survive the create-time strip.
- renderer: lint fix (blank line before comment block).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reject malformed inline-action authorities at render time

- renderer: enforce ^[A-Za-z0-9]+$ on actionId, mirroring the server
  regex. Authorities like mmaction://plan:443 or mmaction://user@plan
  now fall through to plain text instead of rendering a dead button.
- post: clarify in the strip comment that webhooks and plugins bypass
  CreatePostAsUser entirely (they call CreatePost / CreatePostMissingChannel
  directly), so the strip block does not apply to them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Tighten inline-action renderer tests

- Replace oversized-params test with boundary pair (at-cap and over-cap)
  to lock in the > vs >= behavior of the size-limit check.
- Add a "surrounding text survives" assertion for the tag-strip path so
  a future swap from regex strip to a DOM sanitizer won't silently
  drop legitimate content along with tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Inline action buttons via mmaction:// markdown links

Adds inline action buttons rendered from mmaction:// links in markdown,
with the click pipeline reusing the existing post-action infrastructure.
Aligned with the broader mm_blocks_actions framework (Daniel's PR).

* fix lint, DoS hardening, fix and rename test

* Address review feedback

* lint fix

* Reject percent-encoded path traversal in validateIntegrationURL (e.g. %2e%2e%2f) by parsing the URL and checking the decoded path.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-20 13:34:44 -06:00
Scott Bishel
a84941bec1
Remove Legacy Interactive Dialog code (#35874) 2026-05-20 13:34:13 -06:00
Jesse Hallam
0790fc7281
Upgrade Go to 1.26.3 (#36656) 2026-05-20 13:21:14 -03:00
Jesse Hallam
c74e51f35e
chore(ci): upgrade Go to 1.26.3 in build container Dockerfiles (#36648) 2026-05-20 17:56:19 +03:00
Jesse Hallam
51c6d5219f
Fix config Sanitize fields missing from desanitize, causing FakeSetting to be persisted (#36619)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* Add TestDesanitizeRemovesAllFakeSettings to catch future omissions

Walks every string field in the config after a Sanitize+desanitize
round-trip and fails if any still holds FakeSetting. This catches the
case where a field is added to Sanitize without a corresponding
desanitize entry.

* Fix ElasticsearchSettings.ClientKey being incorrectly masked as a secret

ClientKey is a file path, not a secret value. Masking it caused the
asterisk string to be persisted to the database on config writes, which
broke TLS client auth on restart.

* Fix desanitize missing entries for fields added in 504fb96fdd

504fb96fdd masked five fields in Sanitize without adding the
corresponding desanitize entries, meaning a config save through the
API would permanently overwrite those fields with FakeSetting:

- FileSettings.ExportAmazonS3SecretAccessKey
- ServiceSettings.GoogleDeveloperKey
- ServiceSettings.GiphySdkKey
- CacheSettings.RedisPassword
- AutoTranslationSettings.LibreTranslate.APIKey

* fixup! Add TestDesanitizeRemovesAllFakeSettings to catch future omissions

* fixup! Fix desanitize missing entries for fields added in 504fb96fdd

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-20 10:04:21 +00:00
Pablo Vélez
345a0b76a6
Mm 68506 fe abac mask fe table editor cel and e2e (#36517)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* MM-68501 - implement GetMaskedVisualAST and wire API handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add missing test and fix style issues

* fix styles

* implement coderabbit feedback

* MM-68501 - PR review: split masking file, model-level access mode, reject contradictory config

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68501 - apply shared_only filter to non-option field values (binary masking)

* MM-68501 - consolidate masking flag check and log corrupt text value during masking

* MM-68503 - add CEL utilities, write-path validation, and merge helpers

Combined set of helpers consumed by BE-5's save path:

CEL construction / serialization
  - extractStringValues, buildCELFromConditions, conditionToCEL,
    celStringLiteral, celValueLiteral. Used to rebuild a CEL string from a
    VisualExpression, including for GetMaskedExpression on the read-side
    of policy GET / search responses.

Merge-on-save helpers
  - getHiddenValues (per-condition, with pre-fetched fields map for N+1
    avoidance) — finds which stored values are not visible to the caller.
  - mergeConditionValues — re-injects the hidden values into a submitted
    condition without duplicates.
  - Together, these let BE-5 preserve attribute values the caller cannot
    see while still letting them edit the visible parts of a policy.

Write-path value-hold validation
  - validatePolicyExpressionValues, invalidValueError, validateConditionValues.
  - Generic "Invalid value." error on every rejection — no signal about
    whether the value exists or is merely not held (prevents enumeration).
  - Rejects the masked-token sentinel "--------" if submitted as a literal.

These all live in access_control_masking.go alongside the masking primitives
that BE-2 introduced. i18n entries added for the two new error IDs
(app.pap.save_policy.invalid_value, app.pap.validate_expression_values.app_error).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68503 - handle the masked-token sentinel in validation and merge

When the GET /policies endpoint returns a policy via MaskPolicyExpressions,
the raw expression contains the masked-token sentinel "--------" in place
of hidden values. If the frontend round-trips that expression unchanged
back to the server (e.g., the admin only modified channel assignment, not
the rules), the sentinel reaches the save path.

The previous code in validateConditionValues rejected the sentinel as
"Invalid value." This blocks the legitimate round-trip case.

Fix:
  - validateConditionValues: treat the sentinel as a placeholder and skip
    it during visibility / source-only / unknown-mode checks. Other values
    are still validated normally.
  - mergeConditionValues: strip the sentinel from submitted values before
    appending hidden values, so it never propagates to the stored result.
    Both array and single-value forms (string == "--------") are handled.

TestMaskedTokenRejection (which asserted the old rejection behavior) is
replaced by TestMaskedTokenConstant which only verifies the sentinel
string itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68504 - integrate save-path masking: 403 block on delete, merge-on-save, response masking

Save path (CreateOrUpdateAccessControlPolicy):
  * validatePolicyExpressionValues runs on the submitted expression before
    merge so re-injected hidden values are never validated against the
    caller's holdings.
  * mergeStoredPolicyExpressions re-injects hidden values from the stored
    policy and blocks (HTTP 403) any attempt to remove a condition that
    contained values the caller cannot see — closes the row-deletion gap
    in classified environments.
  * mergeExpressionWithMaskedValues unwraps single-element arrays for scalar
    operators after restoring the stored operator (avoids "attr == [val]"
    invalid CEL when the frontend submits "attr in []" as the masked-row
    placeholder for an originally-scalar condition).
  * checkSelfInclusion is bypassed for system admins (they may legitimately
    write conditions for values they do not hold); masking and value-hold
    validation still apply to system admins.

Delete path (DeleteAccessControlPolicy):
  * Same masked-values 403 block — a caller with masked values cannot delete
    the policy at all (UI Delete button is also disabled in FE-3).

Response masking:
  * createAccessControlPolicy and setAccessControlPolicyActiveStatus run
    MaskPolicyExpressions on the response so even a save reply doesn't
    leak the values the caller does not hold.

GetMaskedExpression, maskConditionValuesWithToken, replaceHiddenValuesWithToken,
MaskPolicyExpressions live alongside the rest of the masking helpers in
access_control_masking.go.

team_access_control.go: corrects ValidateChannelEligibilityForAccessControl
call site (drops the spurious receiver and rctx; it's a package-level helper
that only takes channel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68503 - address PR review: batch field fetches, propagate errors, fail-closed write path

* MM-68503 - restore team-admin api4 tests accidentally dropped during BE-5 rebuild

* MM-68503 - address review and CodeRabbit feedback on save-path masking

* add tests for delete masking, self-inclusion, GET mask

* add assertions to strengten tests

* MM-68505 - add has_masked_values type and MaskedChip component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* MM-68506 - add masking support to TableEditor and team settings modal

TableEditor (table_editor.tsx, table_editor.scss):
  - hasMaskedValues plumbed through rows; lock operator/attribute selectors on
    masked rows.
  - Row remove (trash) button disabled on masked rows; disabled-state CSS so
    the icon doesn't show the destructive hover colour or a pointer cursor.
  - Test Rules button disabled when any row has masked values, with tooltip.
  - onMaskedStateChange callback to notify the parent for cross-component
    states (CEL editor read-only, Save disabled, banners).

Value selectors (single_value_selector_menu.tsx, multi_value_selector_menu.tsx,
selector_menus.scss, value_selector_menu.tsx):
  - Append MaskedChip after visible chips on multi-value rows.
  - Render MaskedChip as the sole value on single-value rows where the caller
    holds no visible value.

Policy details (policy_details.tsx, .scss, .test.tsx):
  - Track hasMaskedRows state; receive from TableEditor via onMaskedStateChange.
  - Show masked-values warning banner above the editor when present.
  - Same banner on the Delete confirmation modal so admins understand why
    deletion is consequential.

Team settings modal (team_policy_editor.tsx, .scss):
  - Same masked-values plumbing; delete button uses the disabled state when
    a policy has masked values, regardless of whether channels are assigned.
  - Pre-save check no longer treats "in []" as an incomplete rule — that
    placeholder comes from fully-masked rows that merge-on-save will fill in.

i18n entries added for the new strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68506 - fix hook order in SingleValueSelector when masked state changes

The early return for `hasMaskedValues && !value` sat between useState
and useCallback declarations, so when a parent re-render flipped the
masked state (e.g. after deleting a sibling rule) React saw a different
hook count and crashed with "Rendered fewer hooks than expected".

Move the read-only short-circuit after all hook declarations so the hook
order stays stable across renders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68507 - CEL editor read-only when masked + system console wiring

CEL editor (editor.tsx, editor.scss):
  - hasMaskedRows prop: when true, Monaco is set to read-only and a banner
    explains why ("This expression contains restricted values. Switch to
    Simple mode to edit the values you have access to, or delete the entire
    rule.").
  - Test Rules button disabled in CEL mode when hasMaskedRows is true.

Policy details (policy_details.tsx, .scss):
  - hasMaskedRows state plumbed to CELEditor, TableEditor, and the Save /
    Delete buttons.
  - Save button disabled while masked rows are present (kept after the
    save-allowed-with-masked-values change in BE-5? — no, here we keep Save
    enabled so admins can add/modify rules; only row removal of masked rows
    is blocked).
  - Delete Policy button disabled when hasMaskedRows; a SectionNotice above
    the Delete card explains why ("This policy contains restricted values
    - Deletion not allowed").
  - New save error messages: invalid_value and self_exclusion are surfaced
    from the server's generic responses.

Policies list (policies.tsx): minor wiring change for the new state plumbing.

Table editor (table_editor.tsx): cross-component coordination — emits
onMaskedStateChange and respects the disabled-for-masked-row policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68508 - E2E suite for attribute-value masking

Covers the full read+write masking flow against a real server:
  - Masked chip rendering, operator/attribute lock, Test Rules disabled.
  - System admin subject to masking like any other caller (no role bypass).
  - Save with masked values: hidden values preserved by merge-on-save.
  - Trash button disabled on masked rows; server returns 403 on direct
    API attempt to remove a masked condition.
  - Delete Policy button disabled + server 403 when policy has masked values
    (both system console and team settings modal paths).
  - Self-inclusion failure only fires when the caller holds full visibility.
  - CEL editor read-only with banner when masked rows present.
  - Direct API validation: non-held values and the masked-token sentinel
    rejected with a generic "Invalid value." error.
  - Feature-flag-off path: no masking, all values visible.
  - Text-field shared_only masking (binary) with `in` and `==` operators.

A pluggable DB-setup helper marks specific CPA fields as shared_only for
the duration of a test (with per-test cleanup) since the API blocks setting
access_mode=shared_only without a source_plugin_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68506 - fix lint, jest mock factory, and unreachable delete-modal test

* MM-68506 - localize masked-condition-deleted save error

* MM-68506 - fix masked-policy delete warning detection and localize masked_rule_deleted

* fix linter issues

* MM-68506 - surface delete error, lock value selector on masked rows, drop dead remove-modal

* fix linter, add translations, adjust specs

* import wittoltip from shared

* fix linter and use the correct button variant

* MM-68506 - drop dangling rationale comment in access_control_field_test

* fix linter, translation and e2e tests

* use pg ts types and dependencies for e2e types mocks

* adjust switch mode persistance restriction

* fix team settings style buttons

* fail-closed guard for advanced expressions in  merge-on-save, plus  helper unit tests, and FF/test-helper cleanups

* MM-68505 - add has_masked_values type and MaskedChip component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* MM-68506 - add masking support to TableEditor and team settings modal

TableEditor (table_editor.tsx, table_editor.scss):
  - hasMaskedValues plumbed through rows; lock operator/attribute selectors on
    masked rows.
  - Row remove (trash) button disabled on masked rows; disabled-state CSS so
    the icon doesn't show the destructive hover colour or a pointer cursor.
  - Test Rules button disabled when any row has masked values, with tooltip.
  - onMaskedStateChange callback to notify the parent for cross-component
    states (CEL editor read-only, Save disabled, banners).

Value selectors (single_value_selector_menu.tsx, multi_value_selector_menu.tsx,
selector_menus.scss, value_selector_menu.tsx):
  - Append MaskedChip after visible chips on multi-value rows.
  - Render MaskedChip as the sole value on single-value rows where the caller
    holds no visible value.

Policy details (policy_details.tsx, .scss, .test.tsx):
  - Track hasMaskedRows state; receive from TableEditor via onMaskedStateChange.
  - Show masked-values warning banner above the editor when present.
  - Same banner on the Delete confirmation modal so admins understand why
    deletion is consequential.

Team settings modal (team_policy_editor.tsx, .scss):
  - Same masked-values plumbing; delete button uses the disabled state when
    a policy has masked values, regardless of whether channels are assigned.
  - Pre-save check no longer treats "in []" as an incomplete rule — that
    placeholder comes from fully-masked rows that merge-on-save will fill in.

i18n entries added for the new strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68506 - fix hook order in SingleValueSelector when masked state changes

The early return for `hasMaskedValues && !value` sat between useState
and useCallback declarations, so when a parent re-render flipped the
masked state (e.g. after deleting a sibling rule) React saw a different
hook count and crashed with "Rendered fewer hooks than expected".

Move the read-only short-circuit after all hook declarations so the hook
order stays stable across renders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68507 - CEL editor read-only when masked + system console wiring

CEL editor (editor.tsx, editor.scss):
  - hasMaskedRows prop: when true, Monaco is set to read-only and a banner
    explains why ("This expression contains restricted values. Switch to
    Simple mode to edit the values you have access to, or delete the entire
    rule.").
  - Test Rules button disabled in CEL mode when hasMaskedRows is true.

Policy details (policy_details.tsx, .scss):
  - hasMaskedRows state plumbed to CELEditor, TableEditor, and the Save /
    Delete buttons.
  - Save button disabled while masked rows are present (kept after the
    save-allowed-with-masked-values change in BE-5? — no, here we keep Save
    enabled so admins can add/modify rules; only row removal of masked rows
    is blocked).
  - Delete Policy button disabled when hasMaskedRows; a SectionNotice above
    the Delete card explains why ("This policy contains restricted values
    - Deletion not allowed").
  - New save error messages: invalid_value and self_exclusion are surfaced
    from the server's generic responses.

Policies list (policies.tsx): minor wiring change for the new state plumbing.

Table editor (table_editor.tsx): cross-component coordination — emits
onMaskedStateChange and respects the disabled-for-masked-row policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68508 - E2E suite for attribute-value masking

Covers the full read+write masking flow against a real server:
  - Masked chip rendering, operator/attribute lock, Test Rules disabled.
  - System admin subject to masking like any other caller (no role bypass).
  - Save with masked values: hidden values preserved by merge-on-save.
  - Trash button disabled on masked rows; server returns 403 on direct
    API attempt to remove a masked condition.
  - Delete Policy button disabled + server 403 when policy has masked values
    (both system console and team settings modal paths).
  - Self-inclusion failure only fires when the caller holds full visibility.
  - CEL editor read-only with banner when masked rows present.
  - Direct API validation: non-held values and the masked-token sentinel
    rejected with a generic "Invalid value." error.
  - Feature-flag-off path: no masking, all values visible.
  - Text-field shared_only masking (binary) with `in` and `==` operators.

A pluggable DB-setup helper marks specific CPA fields as shared_only for
the duration of a test (with per-test cleanup) since the API blocks setting
access_mode=shared_only without a source_plugin_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68506 - fix lint, jest mock factory, and unreachable delete-modal test

* MM-68506 - localize masked-condition-deleted save error

* MM-68506 - fix masked-policy delete warning detection and localize masked_rule_deleted

* fix linter issues

* MM-68506 - surface delete error, lock value selector on masked rows, drop dead remove-modal

* fix linter, add translations, adjust specs

* import wittoltip from shared

* fix linter and use the correct button variant

* MM-68506 - drop dangling rationale comment in access_control_field_test

* fix linter, translation and e2e tests

* use pg ts types and dependencies for e2e types mocks

* adjust switch mode persistance restriction

* fix team settings style buttons

* fail-closed guard for advanced expressions in  merge-on-save, plus  helper unit tests, and FF/test-helper cleanups

* Refactor access control methods to use GetPropertyGroup for CPA group ID retrieval

* fix styles

* disable delete on masked policies in list view and remove dead modal warnings

* fix unit tests

* preserve hasAnyOf operator display for fully-masked multiselect conditions

* address PR feedback: lock Actions on masked save, filter source/shared_only from /attributes, add unit tests and e2e tests

* fix e2e tests

* comment out e2e to isolate issue

* completely remove the files to pass linter

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-19 21:25:14 +02:00
Doug Lauder
5566604e03
MM-68838: Ping a restored plugin remote immediately on re-register (#36592)
* MM-68838: ping restored plugin remote immediately on re-register

  RegisterPluginForSharedChannels' restore branch updated the row but did
  not call PingNow, leaving the restored remote offline until the next
  pingLoop tick (up to PingFreq, default 1 minute). The new-connection
  branch already calls PingNow; the restore branch now mirrors it so
  sync attempts immediately after a plugin restart no longer fail with
  "offline remote cluster".

* MM-68838: gob-encode error returns in apiRPCServer.ReceiveSharedChannelAttachmentSyncMsg

  The apiRPCServer wrapper for ReceiveSharedChannelAttachmentSyncMsg
  assigned the hook's error return directly to the gob-encoded response
  struct. When the framework's App.ReceiveSharedChannelAttachmentSyncMsg
  returned an error wrapped with %w (*fmt.wrapError, an unexported type),
  gob refused to encode it and the RPC server broke the connection with
  "type not registered for interface: fmt.wrapError".

  Every subsequent plugin/server RPC call then returned the zero-value
  response struct, causing plugins that dereferenced the nil returns to
  crash.

  Apply the existing encodableError() helper so the returned error
  becomes a gob-safe ErrorString, matching every other apiRPCServer
  method in this file.
2026-05-19 10:12:00 -04:00
Daniel Espino García
92f6870a2b
Add "last used" field for incoming webhooks (#36416)
* Add "last used" field for incoming webhooks

* Address feedback

* Rename migrations

* Fix web lint
2026-05-19 15:22:04 +02:00
Carlos Garcia
548183d748
Mm 68282 admin ephemeral mode (#36194)
* adds feature flag to enable mattermost ephemeral mode

* add ephemeral mode config settings to system console

When feature flag is set to true a new section for Mobile Ephemeral Mode
settings shows under the Mobile Security section in case a valid Enterprise
Advanced License is active.

* adds Mobile Ephemeral Mode settings playwright tests

* improve descriptions for settings

* improves error messages and hints

* move validation to common helper and add new tests

* reverts package-lock.json changes

* proper struct alignment

* proper message sorting in json file

* use generic doc url for MEM section while docs are not ready

* Proper formatting for playwright tests

* fixes test
2026-05-18 22:23:58 +02:00
Julien Tant
9bd77d3fc4
MM-68702: Reject demoting bot accounts to guest (#36487)
* MM-68702: Reject demoting bot accounts to guest

Deny DemoteUserToGuest when the target is a bot so User Managers cannot
degrade bot capabilities via guest conversion without bot administration
permissions. Adds API error string and tests.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

* Fix TestDemoteUserToGuest bot subtest: enable bot creation in config

Default test config disables bot accounts; enable ServiceSettings
EnableBotAccountCreation for the subtest and restore afterward.

Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julien Tant <JulienTant@users.noreply.github.com>
2026-05-18 09:01:00 -07:00
Pablo Vélez
d4471bece1
Mm 68503 be abac mask save path masking (#36513)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* MM-68501 - implement GetMaskedVisualAST and wire API handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add missing test and fix style issues

* fix styles

* implement coderabbit feedback

* MM-68501 - PR review: split masking file, model-level access mode, reject contradictory config

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68501 - apply shared_only filter to non-option field values (binary masking)

* MM-68501 - consolidate masking flag check and log corrupt text value during masking

* MM-68503 - add CEL utilities, write-path validation, and merge helpers

Combined set of helpers consumed by BE-5's save path:

CEL construction / serialization
  - extractStringValues, buildCELFromConditions, conditionToCEL,
    celStringLiteral, celValueLiteral. Used to rebuild a CEL string from a
    VisualExpression, including for GetMaskedExpression on the read-side
    of policy GET / search responses.

Merge-on-save helpers
  - getHiddenValues (per-condition, with pre-fetched fields map for N+1
    avoidance) — finds which stored values are not visible to the caller.
  - mergeConditionValues — re-injects the hidden values into a submitted
    condition without duplicates.
  - Together, these let BE-5 preserve attribute values the caller cannot
    see while still letting them edit the visible parts of a policy.

Write-path value-hold validation
  - validatePolicyExpressionValues, invalidValueError, validateConditionValues.
  - Generic "Invalid value." error on every rejection — no signal about
    whether the value exists or is merely not held (prevents enumeration).
  - Rejects the masked-token sentinel "--------" if submitted as a literal.

These all live in access_control_masking.go alongside the masking primitives
that BE-2 introduced. i18n entries added for the two new error IDs
(app.pap.save_policy.invalid_value, app.pap.validate_expression_values.app_error).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68503 - handle the masked-token sentinel in validation and merge

When the GET /policies endpoint returns a policy via MaskPolicyExpressions,
the raw expression contains the masked-token sentinel "--------" in place
of hidden values. If the frontend round-trips that expression unchanged
back to the server (e.g., the admin only modified channel assignment, not
the rules), the sentinel reaches the save path.

The previous code in validateConditionValues rejected the sentinel as
"Invalid value." This blocks the legitimate round-trip case.

Fix:
  - validateConditionValues: treat the sentinel as a placeholder and skip
    it during visibility / source-only / unknown-mode checks. Other values
    are still validated normally.
  - mergeConditionValues: strip the sentinel from submitted values before
    appending hidden values, so it never propagates to the stored result.
    Both array and single-value forms (string == "--------") are handled.

TestMaskedTokenRejection (which asserted the old rejection behavior) is
replaced by TestMaskedTokenConstant which only verifies the sentinel
string itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68504 - integrate save-path masking: 403 block on delete, merge-on-save, response masking

Save path (CreateOrUpdateAccessControlPolicy):
  * validatePolicyExpressionValues runs on the submitted expression before
    merge so re-injected hidden values are never validated against the
    caller's holdings.
  * mergeStoredPolicyExpressions re-injects hidden values from the stored
    policy and blocks (HTTP 403) any attempt to remove a condition that
    contained values the caller cannot see — closes the row-deletion gap
    in classified environments.
  * mergeExpressionWithMaskedValues unwraps single-element arrays for scalar
    operators after restoring the stored operator (avoids "attr == [val]"
    invalid CEL when the frontend submits "attr in []" as the masked-row
    placeholder for an originally-scalar condition).
  * checkSelfInclusion is bypassed for system admins (they may legitimately
    write conditions for values they do not hold); masking and value-hold
    validation still apply to system admins.

Delete path (DeleteAccessControlPolicy):
  * Same masked-values 403 block — a caller with masked values cannot delete
    the policy at all (UI Delete button is also disabled in FE-3).

Response masking:
  * createAccessControlPolicy and setAccessControlPolicyActiveStatus run
    MaskPolicyExpressions on the response so even a save reply doesn't
    leak the values the caller does not hold.

GetMaskedExpression, maskConditionValuesWithToken, replaceHiddenValuesWithToken,
MaskPolicyExpressions live alongside the rest of the masking helpers in
access_control_masking.go.

team_access_control.go: corrects ValidateChannelEligibilityForAccessControl
call site (drops the spurious receiver and rctx; it's a package-level helper
that only takes channel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68503 - address PR review: batch field fetches, propagate errors, fail-closed write path

* MM-68503 - restore team-admin api4 tests accidentally dropped during BE-5 rebuild

* MM-68503 - address review and CodeRabbit feedback on save-path masking

* add tests for delete masking, self-inclusion, GET mask

* add assertions to strengten tests

* fail-closed guard for advanced expressions in  merge-on-save, plus  helper unit tests, and FF/test-helper cleanups

* Refactor access control methods to use GetPropertyGroup for CPA group ID retrieval

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-18 17:33:13 +02:00
Harshil Sharma
f0360a838a
Data spillage report generation UI (#36340)
* Added base fr report generation

* WIP

* implemented UI flow

* implemented UI flow

* restructured the modal code into sub components

* Refactoring and cleanup

* lint fixes, added new tests

* i18n fix

* test fix

* Updated test

* CI

* Several improvements

* WIP

* Added tests

* Addressed some security enhancements

* Created zip writer entery later

* Improved a test to check for file content

* Improved error handling

* Made a geneeric function

* Updated classes

* accepting comment in report API

* Added more tests

* Integrated new API param

* Removed an unnecessary check

* Made a geneeric function

* Made a geneeric function

* Made the comment body not required and updated API docs

* Updated report generation API call in download report button

* Included decision in report and removed confirmation when keeping message

* Updated test

* Add explicit wait for removeWithoutReportButton visibility in test

Prevent race condition by waiting for the button to be visible after UI transitions to skip-confirm step before clicking it.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

* PR Feedback

* explicitelly added return statement

* Included actor details in report

* Updated tests

---------

Co-authored-by: maria.nunez <maria.nunez@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-18 20:24:50 +05:30
Miguel de la Cruz
9d06155540
Update bot checks (#36503)
* Fix bot permission checks in revokeSession, revokeAllSessionsForUser, and updatePassword

MM-68701: Align permission checks with the bot-aware pattern used by
updateUser, patchUser, deleteUser, and (via MM-68686) updateUserActive.

Three handlers were missing the IsBot branch:

- revokeSession / revokeAllSessionsForUser: both gated access through
  SessionHasPermissionToUser, which only requires EditOtherUsers (an
  ancillary permission granted to User Managers). Switching to
  SessionHasPermissionToUserOrBot routes bot targets through
  SessionHasPermissionToManageBot first and falls back to the user
  path only when the target is not a bot.

- updatePassword: the permission flag canUpdatePassword was set by
  checking PermissionSysconsoleWriteUserManagementUsers (or
  PermissionManageSystem for system admins) with no IsBot branch.
  Adding an else-if user.IsBot guard routes bot targets through
  SessionHasPermissionToManageBot, consistent with every other
  handler in the file that touches bot accounts.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Improve TestRevokeSessionBotPermissions: revoke a real bot session

Seed a session directly via th.App.CreateSession instead of passing a
fake ID and expecting a 400. The test now validates the full happy path:
the session row is created, the privileged user revokes it, and the
call returns 200 OK.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Strengthen forbidden sub-test: revoke a real bot session with no perms

Seed a real session for the bot before the unprivileged revoke call.
The test now proves the permission gate blocks access even when the
target session ID genuinely exists in the database.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Address review feedback: add post-conditions to bot session revoke tests

- TestRevokeSessionBotPermissions: after RevokeSession succeeds, assert
  GetSessionById returns an error to confirm the row is gone.
- TestRevokeAllSessionsForUserBotPermissions: seed a real session before
  RevokeAllSessions so the call is not a no-op, then assert GetSessions
  returns an empty list afterwards.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-18 12:27:38 +02:00
Miguel de la Cruz
669eb104c6
Fix webhook list ordering instability when paginating (MM-65732) (#36470)
* Fix webhook list ordering instability when paginating (MM-65732)

The webhook list view reorders entries when navigating between pages.
The first page initially shows webhooks in insertion order (from the
server), but after loading additional pages the display settles into
alphabetical order. Going back to page 1 then shows different items
than were originally visible.

Root causes:
1. Server: GetIncomingByTeamByUser, GetIncomingListByUser,
   GetOutgoingByTeamByUser, and GetOutgoingListByUser had no ORDER BY
   clause, so the database could return rows in any order.
2. Client (incoming webhooks): incomingWebhookCompare only resolved
   the channel-name fallback for the 'a' argument, not 'b', making
   the comparator asymmetric and producing an unstable sort.
3. Client: both installed_incoming_webhooks and installed_outgoing_webhooks
   called Array.prototype.sort() directly on the props array, mutating it.

Fix:
- Add ORDER BY DisplayName, Id to the four listing SQL queries so API
  pages always come back in alphabetical order. With a stable server
  order, the client sort over merged pages produces the same slice for
  each page number regardless of how many pages have been loaded.
- Symmetrise incomingWebhookCompare by applying the same channel-name
  and 'Private Webhook' fallback to the 'b' argument.
- Sort a copy ([...hooks].sort()) in both webhook list components so
  the original prop arrays are never mutated.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Fix lint: remove space before JSX closing tag in webhook test

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Fold ordering tests into existing webhook store test functions

Instead of four separate top-level test registrations
(GetIncomingListByUserOrdering, etc.), each ordering assertion is
now a t.Run sub-test inside its corresponding existing function:

  testWebhookStoreGetIncomingListByUser
    └─ "GetIncomingListByUser, ordered alphabetically by display name"

  TestWebhookStoreGetIncomingByTeamByUser
    └─ "GetIncomingByTeamByUser, ordered alphabetically by display name"

  testWebhookStoreGetOutgoingListByUser
    └─ "GetOutgoingListByUser, ordered alphabetically by display name"

  testWebhookStoreGetOutgoingByTeamByUser
    └─ "GetOutgoingByTeamByUser, ordered alphabetically by display name"

Each sub-test creates fresh hooks (Charlie, Alpha, Bravo in insertion
order) scoped to its own IDs so they do not interfere with the
outer test's fixtures.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Fix govet shadow and gofmt issues in webhook store tests

- Rename the outer err variable to errSave in three functions
  (testWebhookStoreGetIncomingListByUser,
  TestWebhookStoreGetIncomingByTeamByUser,
  testWebhookStoreGetOutgoingByTeamByUser) so that hooks, err :=
  declarations in sub-test closures no longer shadow it.
- Change hookC, err = to hookC, err := in each ordering sub-test
  to declare a local err instead of capturing the outer one.
- Remove a trailing blank line at the end of the file (gofmt).

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Remove jest.mock from installed_incoming_webhooks test

The mock for delete_integration_link was copied from the outgoing
webhooks list test but is not needed here: the real component renders
fine within renderWithContext (as shown by installed_incoming_webhook.test.tsx
which tests the individual item without any mocks). Since the ordering
tests do not interact with delete functionality, drop the mock and align
the action stub style to mockReturnValue(Promise.resolve()).

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

* Add ORDER BY to GetOutgoingByChannelByUser; add ordering sub-test

GetOutgoingByChannelByUser was the last paginated webhook listing
function without an ORDER BY clause. Add OrderBy("DisplayName", "Id")
consistent with all other listing functions.

Add the corresponding ordering sub-test inside
testWebhookStoreGetOutgoingByChannelByUser, following the same
errSave pattern established for the other functions to avoid
govet shadow warnings.

Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Miguel de la Cruz <mgdelacroix@users.noreply.github.com>
2026-05-18 12:25:05 +02:00
Alejandro García Montoro
238867e247
MM-68732: Remove global mutex for login attempts in favour of database serialization (#36515)
* Add atomic login-attempt counter primitives to UserStore

Two new store methods back the upcoming switch from a global
per-node mutex to per-user atomic slot claiming:

  TryIncrementFailedPasswordAttempts(userID, maxAttempts) (bool, error)
    UPDATE Users SET FailedAttempts = FailedAttempts + 1
                 WHERE Id = ? AND FailedAttempts < maxAttempts
    Returns true when a slot was claimed (rows affected == 1) and
    false when the cap was already reached. The conditional UPDATE
    serialises concurrent attempts on the same user via the row
    lock, so the cap is enforced without any application-level
    locking and without serialising attempts across users.

  DecrementFailedPasswordAttempts(userID) error
    UPDATE Users SET FailedAttempts = FailedAttempts - 1
                 WHERE Id = ? AND FailedAttempts > 0
    Releases a slot previously claimed by TryIncrement when the
    in-flight authentication turns out not to be a credential
    failure. The conditional UPDATE means concurrent decrements
    cannot underflow.

Storetest covers both primitives: claim-below-cap, reject-at-cap,
reject-above-cap, no-op for unknown user, and a 50-goroutine
concurrent test with a start barrier asserting exactly
maxAttempts slots are ever claimed and that decrement clamps at
zero under contention.

The testify mock is regenerated here so the storetest package
that returns *mocks.UserStore as a store.UserStore still satisfies
the interface; the wrapper layers are regenerated in the next
commit.

------
AI assisted commit

* Regenerate store layers for the new primitives

Pick up TryIncrementFailedPasswordAttempts and
DecrementFailedPasswordAttempts in every generated wrapper:

  - retrylayer: retry on repeatable errors using the standard
    three-attempt loop.
  - timerlayer: record store-method duration metrics under
    UserStore.TryIncrementFailedPasswordAttempts and
    UserStore.DecrementFailedPasswordAttempts.
  - localcachelayer: invalidate the profile cache only after the
    underlying conditional UPDATE actually changes a row; an
    at-cap no-op return on TryIncrement no longer produces
    unnecessary cluster invalidation traffic.

------
AI assisted commit

* Drop login-attempt mutex; use per-user slot claiming

Replace the global per-node mutex that serialised every login
attempt with the database-side atomic slot machine added on the
Users row. Each of the three authentication entry points now
pre-claims a slot via TryIncrementFailedPasswordAttempts before
running the expensive password / LDAP / MFA check, and releases
the slot when the failure path is not a real credential mismatch:

  - CheckPasswordAndAllCriteria (email/password): refunds the
    slot on backend errors during the password check (malformed
    stored hash, hasher misc failure, password-migration write
    failure) so a transient infra issue cannot ratchet
    FailedAttempts to a lockout for a user with valid credentials;
    refunds on the MFA pre-flight probe (empty mfaToken on an
    MFA-enabled user) so the probe is not counted as a real
    attempt.
  - DoubleCheckPassword: same backend-error refund predicate.
  - checkLdapUserPasswordAndAllCriteria: pre-claims only for
    existing users (first-time LDAP users have no local row to
    claim against); refunds non-credential DoLogin errors (server
    unreachable, transient) so an LDAP outage cannot lock out
    everyone; refunds the MFA pre-flight probe; for first-time
    users, explicitly bumps the counter via UpdateFailedPasswordAttempts
    on a real bad-password or bad-MFA attempt, matching the
    pre-refactor counting behaviour.

If the refund itself fails the underlying authentication error is
preserved and returned to the caller (the failure is logged); a
leaked slot is annoying, but masking the real failure with a
generic store 500 would be a clear observability regression.

Cluster-wide behaviour also changes: the previous design honoured
MaximumLoginAttempts per node, so an n-node cluster effectively
permitted n * MaximumLoginAttempts attempts. The cap is now
enforced globally.

------
AI assisted commit

* Cover app-layer behaviors of the new login slot machine

The store-layer tests already exercise TryIncrement and Decrement
under concurrency and at the cap boundary. The new behavioural
contracts at the app layer were not covered, so a regression that
flipped a refund predicate, a probe condition, or a first-time
LDAP path would have slipped through type checking and existing
unit tests.

Add tests around the three callers of the new path:

  - CheckPasswordAndAllCriteria: an MFA pre-flight probe (empty
    token) does not consume a slot; a real attempt with a wrong
    non-empty token does; a backend error during the password
    check (malformed stored hash) refunds the slot; the happy
    path also asserts FailedAttempts resets to zero.
  - DoubleCheckPassword: gets its first test coverage, covering
    the happy path, rate-limit rejection once max attempts is
    reached, and the backend-error refund path.
  - checkLdapUserPasswordAndAllCriteria: covers paths the table
    loop did not exercise, first-time LDAP user with a bad
    password (uses GetUserByAuth to reach the freshly created
    row), first-time LDAP user with a wrong MFA token, existing
    LDAP user with a non-credential DoLogin error (slot
    refunded), and the existing LDAP user MFA pre-flight probe
    (slot refunded).

------
AI assisted commit

* Address coderabbit review

------
AI assisted commit

* Fix race in first-time LDAP failed-attempt counter

For first-time LDAP users we have no local row to pre-claim, so
the bad-password and bad-MFA branches fell back to an absolute
UpdateFailedPasswordAttempts(id, ldapUser.FailedAttempts+1) based
on a snapshot from GetUserByAuth. Concurrent first-attempt
requests for the same user could all read FailedAttempts == 0 and
all write 1, losing increments. As a secondary issue the absolute
set did not enforce MaximumLoginAttempts, so the counter could
also drift past the cap.

Switch both branches to TryIncrementFailedPasswordAttempts, the
atomic conditional UPDATE already used on every other path. The
row lock serialises concurrent increments and the predicate caps
at MaximumLoginAttempts.

A new concurrent storetest-style subtest runs
3 * maxFailedLoginAttempts goroutines through the first-time
bad-password path against the same fresh LDAP row and asserts
FailedAttempts lands at exactly maxFailedLoginAttempts. Against
the previous absolute-set implementation the test fails (observed
FailedAttempts = 4 with maxFailedLoginAttempts = 3, either a lost
increment or a cap overshoot).

The first-time bad-password branch also switches from a wrapped
500 return on store error to log-and-continue, matching the rest
of the file's refund/probe error handling: the underlying LDAP
authentication failure is the more useful error for the caller.

------
AI assisted commit

* Address review comments

------
AI assisted commit

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-18 10:16:32 +00:00
Ben Cooke
02023f0328
[MM-68463] New endpoint to GET user by auth_data (#36352)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
2026-05-15 15:26:03 -04:00
Ibrahim Serdar Acikgoz
deafd88fd5
MM-68762: Discoverable Private Channels — Server data layer (#36539)
* MM-68762: Add Postgres migrations for discoverable private channels

Three online-safe migrations introduce the schema that supports the
Discoverable Private Channels feature (PRs 2-5 of MM-68430 will land
behind it):

- 000175 adds Channels.Discoverable BOOLEAN NOT NULL DEFAULT FALSE.
  Metadata-only on Postgres >= 11; no table rewrite.
- 000176 creates a partial index on
  (TeamId) WHERE Discoverable AND Type='P' AND DeleteAt=0
  using CREATE INDEX CONCURRENTLY (-- morph:nontransactional) so the
  build never blocks writes on the populated Channels table.
- 000177 creates the ChannelJoinRequests table with three indexes, the
  important one being the partial unique index on (ChannelId, UserId)
  WHERE Status = 'pending'. That keeps the full audit history intact
  while still enforcing at-most-one active pending request per
  (channel, user).

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add FeatureFlagDiscoverableChannels (default false)

Gates the per-channel Discoverable toggle and the channel-join-request
flow. Default-OFF so all PRs in the MM-68430 series can land on master
without exposing partial UX.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add Discoverable + ChannelJoinRequest models

- Channel gains a Discoverable bool, ChannelPatch a *bool, both serialized
  as 'discoverable'. Patch() applies it, Auditable() logs it, and IsValid()
  rejects Discoverable=true on any non-private channel so a misconfigured
  patch can never produce a public discoverable channel.
- New ChannelJoinRequest type captures the per-row state of a non-member's
  request: pending -> approved | denied | withdrawn. Rows are append-only
  with reviewer and timestamps so the table is also the audit trail.
  IsValid() enforces:
  * recognized status,
  * Message and DenialReason rune limits,
  * DenialReason only on denied rows (no orphan reasons),
  * reviewer + reviewed_at present for any terminal review (approved /
    denied) but not for self-service withdrawal.
- Two new WebSocket event constants -- channel_join_request_created and
  channel_join_request_updated -- that later PRs broadcast on the admin
  queue and the requester's My Pending Requests panel.

Unit tests cover Patch(), the new IsValid() rule on Discoverable, the
PreSave/PreUpdate timestamp behavior on ChannelJoinRequest, and every
IsValid branch including the reviewer-required-on-review invariant.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add discoverable-channel permissions

Two new channel-scoped permissions, each independently rebindable from
the System Console:

- manage_private_channel_discoverability gates the per-channel toggle so
  admins can restrict who can flip discoverability without also handing
  out manage_private_channel_properties.
- manage_channel_join_requests gates the queue list / approve / deny /
  count endpoints (added in PR 2).

Both are added to the channel_admin role bootstrap so new deployments
get them by default, and a new permissions migration
(add_discoverable_channel_permissions) grants them to channel_admin,
team_admin and system_admin scheme roles on existing deployments.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add ChannelJoinRequestStore and wire Discoverable into channel store

- channelSliceColumns / channelToSlice / updateChannelT now include the
  new Discoverable column so Save() and Update() round-trip the field.
  Existing select paths inherit the column automatically because every
  read goes through channelSliceColumns.
- New ChannelJoinRequestStore interface and SQL implementation:
  Save / Get / GetPendingForChannelAndUser / GetForChannel / GetForUser
  / Update / CountPending. Save translates the
  idx_channeljoinrequests_pending_unique partial unique index violation
  into store.ErrConflict so the app layer (PR 2) can return 409 without
  re-parsing pq errors.
- Storetest suite at storetest/channel_join_request_store.go is invoked
  from sqlstore via the existing StoreTest harness; covers insert /
  partial-unique conflict / re-insert after withdrawal / NotFound /
  status filtering / pagination with TotalCount / Update / CountPending.
- Mocks and retrylayer / timerlayer are regenerated via make store-mocks
  and go generate ./channels/store -- no hand-written generator output.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add TS types for Discoverable channels + join requests

webapp/platform/types:
- Channel.discoverable?: boolean alongside existing policy_enforced /
  policy_is_active so the web client sees the same wire shape the server
  emits.
- ChannelJoinRequest, ChannelJoinRequestStatus, ChannelJoinRequestList,
  GetChannelJoinRequestsOptions for the API contract surfaced in PR 2.

webapp/platform/client:
- WebSocketEvents enum gains ChannelJoinRequestCreated and
  ChannelJoinRequestUpdated so PR 3 can hang WS handlers off them
  without redeclaring constants.

These are model-only updates with no UI consumer yet; PR 3 introduces
the toggle, request flow, and admin queue surfaces.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Split ChannelJoinRequests indexes into concurrent migrations

The mattermost-govet concurrentIndex lint check enforces CREATE INDEX
CONCURRENTLY on every CREATE INDEX statement, even on an empty
freshly-created table where it would be a no-op. The original 000177
file inlined three CREATE INDEX statements; that failed check-style.

Mirror the convention used by 000166_create_views +
000167_create_views_channel_id_delete_at_index: keep the CREATE TABLE
in its own (transactional) file, and move each index into a separate
nontransactional file that runs CREATE INDEX CONCURRENTLY. Verified
locally against Postgres 15 that all four new migrations apply in
order and the storetest suite (partial unique constraint + paged
list + count) still passes.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Wire new permission migration into test fixtures

Two CI test surfaces missed when the channel_admin role and the
permission-migration list gained the new
manage_private_channel_discoverability and manage_channel_join_requests
entries:

- testlib/store.go: the shared mocked SystemStore used by
  SetupWithStoreMock / SetupEnterpriseWithStoreMock needs an explicit
  GetByName expectation for every migration key (because the mock
  panics on unexpected calls). Add the new
  MigrationKeyAddDiscoverableChannelPermissions key so
  TestCreateOrUpdateAccessControlPolicy, the elasticsearch
  aggregation_job_test, and every other mock-store test stop panicking
  on server bootstrap.
- cmd/mmctl/commands/permissions_test.go: TestResetPermissionsCmd
  hard-codes the channel_admin default permission list and expects
  PatchRole to be called with exactly that slice. Extend the expected
  slice with the two new permission ids so the mmctl reset path stays
  in sync with the role bootstrap.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Register new idx_channels_discoverable_team in TestGetSchemaDefinition

The schema-dump test asserts an exact index count and definition map
for the channels table. Migration 000176 added
idx_channels_discoverable_team — a partial btree on (teamid) gated by
discoverable=true AND type='P' AND deleteat=0. Bump the expected count
from 12 to 13 and add the index's CREATE INDEX definition as produced
by pg_indexes (note: type is cast to channel_type, the existing
domain). Verified locally against Postgres 15.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Fix golangci-lint findings in ChannelJoinRequest store

Two golangci-lint findings on the freshly-added files:

- sqlstore/channel_join_request_store.go:133 (modernize): collapse the
  'if page < 0 { page = 0 }' clamp into max(opts.Page, 0).
- storetest/channel_join_request_store.go:243 (govet shadow): the
  inner Save loop redeclared err with :=, shadowing the outer err
  captured from the first CountPending call. Switch to plain
  assignment so the same err is reused.

Verified locally with golangci-lint v2.11.4 across public/...,
channels/app/..., channels/store/..., channels/testlib/... and
cmd/mmctl/commands/... — 0 issues.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Sync channel_admin bootstrap with TestDoAdvancedPermissionsMigration

app_test.go pins the exact list of permissions the channel_admin role
is expected to hold after DoAdvancedPermissionsMigration completes.
The role bootstrap in role.go grew two entries
(manage_private_channel_discoverability and manage_channel_join_requests),
so the test's expected slice needs the same two entries appended in
the same order, otherwise assert.Equal fails on slice ordering.

This is the same class of fix as the mmctl/permissions_test.go change
in a previous commit -- two parallel test fixtures encode the
channel_admin defaults and have to be updated in lockstep with the
bootstrap.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Add English translations for new model error keys

12 keys were emitted by the new Discoverable + ChannelJoinRequest
validation paths but had no en.json entry, which trips i18n-check on
CI. Add the missing entries with one-line English copy that mirrors
adjacent model errors (Invalid <field>., Create at must be a valid
time., etc.). The new entries are:

- model.channel.is_valid.discoverable.app_error
- model.channel_join_request.is_valid.channel_id.app_error
- model.channel_join_request.is_valid.create_at.app_error
- model.channel_join_request.is_valid.denial_reason.app_error
- model.channel_join_request.is_valid.denial_reason_status.app_error
- model.channel_join_request.is_valid.id.app_error
- model.channel_join_request.is_valid.message.app_error
- model.channel_join_request.is_valid.reviewed_by.app_error
- model.channel_join_request.is_valid.reviewer.app_error
- model.channel_join_request.is_valid.status.app_error
- model.channel_join_request.is_valid.update_at.app_error
- model.channel_join_request.is_valid.user_id.app_error

Generated through 'make i18n-extract'; verified clean with
'make i18n-check'. Per the workspace rule, only en.json was modified --
no other locale files.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Address CodeRabbit review: stable pagination + redact denial reason from audit log

Two production-code findings from CodeRabbit on the freshly-added
ChannelJoinRequest server code:

- sqlstore/channel_join_request_store.go (GetForChannel / GetForUser):
  OrderBy("CreateAt DESC") alone is unstable when two rows share a
  millisecond (NewId is monotonic-ish but CreateAt is millisecond
  resolution), so offset paging could duplicate or skip rows between
  pages. Add Id DESC as a deterministic tie-breaker on both list
  queries.
- model/channel_join_request.Auditable: the denial reason is admin-typed
  free text and could carry sensitive content. Mirror the existing
  has_message pattern by emitting has_denial_reason as a boolean
  presence flag instead of the raw value. Reviewer id, review timestamp,
  and status are still logged, so the audit trail keeps every piece
  needed for compliance review.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Tighten model tests per CodeRabbit review

Two test-only findings from CodeRabbit:

- TestChannelJoinRequestPreUpdateAdvancesUpdateAt previously asserted
  GreaterOrEqual(r.UpdateAt, originalCreate). Because validRequest
  initialises UpdateAt to GetMillis() (same call site as CreateAt), a
  no-op PreUpdate would still pass that check. Seed r.UpdateAt = 1
  before calling PreUpdate() and assert Greater(r.UpdateAt, int64(1))
  so any regression that drops the GetMillis assignment fails the test.
- TestChannelIsValidDiscoverable did not cover ChannelTypeGroup. Add the
  case alongside ChannelTypeOpen and ChannelTypeDirect so the contract
  that 'only ChannelTypePrivate accepts Discoverable=true' is fully
  pinned across all four channel types.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

* MM-68762: Mock ChannelJoinRequest accessor in retrylayer test

retrylayer_test.go's genStore() helper mocks every Store() accessor
because retrylayer.New() wraps the entire surface. The new
ChannelJoinRequest() method I added on Store was missing from the
mock, so TestRetry/on_regular_error_should_not_retry panicked with
'Unexpected Method Call ChannelJoinRequest()' on Postgres shard 0.

Add the mock alongside the other accessors. No production code
change.

Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
2026-05-15 21:04:32 +02:00
Jesse Hallam
3f3d8408b2
Return descriptive errors from Role.IsValid and Role.IsValidWithoutId (#36582)
* Return descriptive errors from Role.IsValid and Role.IsValidWithoutId

Previously both methods returned bool, leaving callers with no context
about which validation check failed. Now both return error with a
message identifying the specific constraint that was violated.

* Add tests for Role.IsValid and Role.IsValidWithoutId

* Log migration key on doPermissionsMigration failure

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-15 18:40:25 +00:00
Bill Gardner
fa1255f149
Update Calls to v1.11.5 (#36574)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
2026-05-15 09:22:08 -04:00
Jesse Hallam
54bee00622
MM-68332: consistently enforce query timeouts (#36522)
* Remove QueryRowx (no-timeout) from sqlxDBWrapper; migrate caller to QueryRowX

QueryRowx forwarded to context.Background() with no timeout, while QueryRowX
(uppercase) already enforces the wrapper timeout. Removing the no-timeout
variant eliminates an accidental footgun and migrates the one sqlstore caller
(plugin_store) to the timeout-enforcing method.

* sqlxRow, with timeout cancel after Scan

Introduce sqlxRow, which pairs *sqlx.Row with its context cancel function.
QueryRowX (on both sqlxDBWrapper and sqlxTxWrapper) now returns *sqlxRow;
Scan calls cancel immediately after the row is consumed, releasing the
timeout context as soon as possible rather than waiting for the timer.

* sqlxRows, with timeout cancel on Close

Introduce sqlxRows, which embeds *sqlx.Rows and holds the timeout context's
cancel function. Close() cancels the context immediately after the rows are
done, releasing timeout resources as soon as iteration completes rather than
waiting for the timer to fire.

Introduce rowScanner interface (Next/Scan/Err) so the internal helpers
scanRowsIntoMap and scanRetentionIdsForDeletion accept any row iterator
rather than the concrete *sql.Rows, accommodating the new return types
without threading *sqlxRows through every caller.

* abolish the X suffix altogether

* delete unused NamedQuery

* add timeout tests for Query and QueryRow on db and tx wrappers

* fix tx/timeout tests: handle pq driver.ErrBadConn on killed connection

* fixup! fix tx/timeout tests: handle pq driver.ErrBadConn on killed connection

* override Next() on sqlxRows to cancel on EOF

* rm redundant row.Err(), handled by Scan

* apply timeout to context unless deadline set

* rebind consistently

* address review feedback: rename, trace, and QueryContext fix

- Rename withQueryTimeout → ensureQueryTimeout to better convey that it
  respects existing deadlines rather than overriding them.
- Add missing w.trace blocks to QueryRowContext and ExecContext.
- Change QueryContext to use ensureQueryTimeout and return *sqlxRows
  (instead of *sql.Rows) so the cancel is deferred to Close/Next
  rather than released prematurely.

* fix Beginx → Begin after master merge

* fix golangci-lint inline warning in sqlx_wrapper_test

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-15 13:02:13 +00:00
Jesse Hallam
d4fc0ecb1c
MM-68150: Upgrade golangci-lint to v2.12.2 (#36554)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* Simplify invite_people email parsing

Replace backwards in-place mutation loop with a straightforward forward
filter into a new slice. Extract into parseEmailList so the logic can be
unit tested directly.

* MM-68150: Upgrade golangci-lint to v2.12.2

Remove //go:fix inline from NewPointer, which is a generic function not
yet supported by the inline analyzer, and fix 11 slicesbackward
modernize issues flagged by the new version.

* MM-68150: Enable all linters by default; disable those with >20 existing issues

Switch from opt-in (default: none) to opt-out (default: all) so new
linters added to golangci-lint are evaluated automatically. Explicitly
disable every linter that has more than 20 pre-existing violations,
deferring those for later cleanup. Also disable a handful of linters
whose violations are intentional patterns in this codebase (nilerr,
dogsled, sqlclosecheck, iotamixing, predeclared, containedctx, iface,
gocheckcompilerdirectives, promlinter, goprintffuncname, gomoddirectives).

* MM-68150: Fix mirror linter issues

Replace Write([]byte(s)) with WriteString(s), and FindIndex([]byte(s))
with FindStringIndex(s), to avoid unnecessary allocations.

* MM-68150: Fix nosprintfhostport linter issue

Use net.JoinHostPort to construct host:port strings instead of
fmt.Sprintf with a manually formatted pattern.

* MM-68150: Fix rowserrcheck and sqlclosecheck linter issues

Check rows.Err() after iteration loops in schema_dump.go. In the
sqlx_wrapper test, defer rows.Close() rather than closing inline.

* MM-68150: Fix nilnesserr linter issues — wrong variable in error handlers

In 11 places, a stale variable (often the outer err from a prior
assignment) was used instead of the freshly-checked error variable
(appErr, rowErr, jsonErr, writeErr, esErr). Each produces a typed-nil
wrapped in a non-nil interface, silently discarding the real error.

* MM-68150: Add i18n string for app.compile_csv_chunks.write_error

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-14 17:29:37 -04:00
Julien Tant
d43dbe972e
Update Playbooks plugin to v2.9.0 (incl. FIPS) (#36570) 2026-05-14 12:37:14 -07:00
Alejandro García Montoro
f604ec7a5c
MM-68662: Add Azure Blob Storage filestore backend (#36498)
* Generalize file backend error types

Replace S3FileBackendAuthError and S3FileBackendNoBucketError with
backend-agnostic FileBackendAuthError and FileBackendNoBucketError so
non-S3 drivers can return them and the admin "Test Connection" flow
keeps surfacing useful messages.

The old S3-prefixed names are kept as type aliases of the generic
types so external code (plugins, historical consumers) continues to
compile, and so existing S3 construction sites stay untouched.

The type switch in connectionTestErrorToAppError now matches the
generic types, with new i18n keys (test_connection_auth.app_error
and test_connection_no_bucket.app_error) whose wording does not name
S3. The old S3-specific i18n keys are dropped via `make i18n-extract`
since they are no longer referenced from code; the api4 test that
asserted on those keys is updated, and the Cypress
`MM-T996 Amazon S3 connection error messaging` spec that asserted
on the old user-facing string is updated to the new wording.

------
AI assisted commit

* Pull in Azure SDK and uuid dependencies

Bring in github.com/Azure/azure-sdk-for-go/sdk/azcore and
.../sdk/storage/azblob (with .../sdk/internal as their indirect
dependency). The two are needed by the upcoming Azure Blob Storage
filestore backend and its lazy-Range-backed reader. The bump of
golang.org/x/{crypto,net,sys,term,text} comes transitively from
azblob's minimum versions.

Also promotes github.com/google/uuid from indirect to direct,
since the Azure backend uses it to generate block IDs that share
the same wire format the SDK itself produces in UploadStream.

------
AI assisted commit

* Add azureRangeReader, a seekable Range-backed blob reader

A small standalone type that satisfies the FileBackend interface's
ReadCloseSeeker + the broader io.ReaderAt contract on top of Azure
Blob Storage HTTP Range requests. Lands as its own commit because
the upcoming Azure FileBackend driver builds on it, and the reader
itself is independently useful — and independently testable against
a fake downloader without standing up an Azure client.

Design notes:

* Read opens an HTTP Range stream lazily at the current offset and
  reuses it for sequential reads. Seek to a different offset closes
  the open stream; the next Read re-opens it.
* Seek to the same offset is a no-op and does not close the open
  stream, so callers like zip.NewReader that probe with redundant
  seeks don't kick off a fresh download.
* ReadAt issues a dedicated ranged DownloadStream per call and does
  not touch the streaming cursor — matches the io.ReaderAt contract
  the bulk-import worker's zip.NewReader path relies on.
* Close cancels the context (which any in-flight Azure call will
  observe and abort), stops the deadline timer, and closes the
  current body if any. It is safe to call when no body was ever
  opened.
* CancelTimeout lets long-running consumers like the import worker
  opt out of the per-operation deadline that would otherwise kill
  multi-minute downloads partway through.

The implementation talks to a small blobDownloader interface rather
than *blob.Client directly so the unit tests can substitute a fake
downloader that records every requested Range and tracks Close
calls on the bodies it hands out.

------
AI assisted commit

* Add Azure Blob Storage filestore driver

Implements the FileBackend interface against Azure Blob Storage in
a new azurestore.go (~520 LOC). The driver is not yet selectable
via NewFileBackend's switch — that wiring lands in the next commit
together with the admin config surface — but the driver itself is
complete and self-contained behind the FileBackendSettings struct.

Filesstore.go grows three pieces of supporting infrastructure that
the driver consumes:

* a `driverAzure = "azureblob"` constant alongside the existing
  driverS3 and driverLocal,
* an Azure-specific block on FileBackendSettings (storage account,
  access key, container, path prefix, endpoint, SSL flag, request
  timeout),
* a CheckMandatoryAzureFields validator that mirrors
  CheckMandatoryS3Fields.

Behavioural notes that warrant calling out:

* Reader returns the previously-added azureRangeReader, so reads
  stream lazily over HTTP Range and ReadAt is available for the
  bulk-import worker's zip.NewReader path. The deadline timer is
  armed before the initial GetProperties call so the HEAD itself
  is bounded.
* WriteFile and AppendFile both go through StageBlock +
  CommitBlockList via a shared stageBlocks helper, never the SDK's
  UploadStream. UploadStream's small-payload fast path falls back
  to single-shot PutBlob, which leaves the resulting blob with no
  committed block list; a subsequent AppendFile that calls
  CommitBlockList on that blob would then clobber its content.
  Routing every write through the block-list mechanism keeps
  AppendFile correct regardless of payload size.
* AppendFile stages the new chunk as one or more blocks and commits
  the existing committed block list plus the newly staged IDs.
  The new bytes go up exactly once — no re-download, no
  re-concatenate, no re-upload of the prior contents.
* WriteFileContext does not wrap the caller-supplied context with
  its own timeout — that timeout is applied in WriteFile only,
  matching the S3 driver, so long-running TryWriteFileContext
  callers (like message-export bulk writes) opt out of the
  per-operation timeout the way the abstraction documents.

Authentication is shared-key only for this drop; Microsoft Entra
ID / managed identity is deferred to a follow-up. The endpoint is
configurable so the same code targets the production Azure host
(vhost style — {account}.blob.core.windows.net) or Azurite /
Azure Government / sovereign clouds (path style —
host[:port]/{account}).

------
AI assisted commit

* Wire Azure backend into config, validation, and driver selection

This commit registers the previously-added AzureFileBackend driver
with the rest of the system. Until now the driver was usable only
via direct construction; after this commit, `DriverName: "azureblob"`
in config.json is a fully-supported deployment configuration.

Five integration sites are touched:

* `newFileBackend` in filesstore.go now dispatches `driverAzure` to
  NewAzureFileBackend, alongside the existing s3 and local cases.
  NewFileBackendSettingsFromConfig (and its export counterpart) gain
  an Azure branch that maps the model.FileSettings fields onto the
  Azure-specific FileBackendSettings fields.
* `model.FileSettings` grows the user-facing Azure config schema:
  storage account, access key, container, path prefix, endpoint,
  SSL flag, request timeout, plus matching Export* fields for the
  dedicated export store. SetDefaults populates them so deployments
  that never opted into Azure don't carry nil pointers. `isValid`
  accepts the new ImageDriverAzure constant.
* `Config.Sanitize()` masks AzureAccessKey and ExportAzureAccessKey
  the same way it masks AmazonS3SecretAccessKey, so the shared key
  never reaches an API consumer in plain text.
* `desanitize()` restores the masked keys on a config write so a
  PATCH that doesn't touch the key doesn't clobber it with the
  FakeSetting placeholder.
* `configSensitivePaths` covers both Azure key paths so audit
  diffs don't include them either.
* `ConfigToFileBackendSettings` in the `mattermost db` CLI helper
  gets the Azure branch its production counterpart already has —
  without it, `mattermost db migrate` / `db downgrade` would fail
  on Azure-configured deployments with "missing azure storage
  account setting".

Finally, the shared FileBackendTestSuite is now wired against
Azurite via TestAzureFileBackendTestSuite, which skips when
CI_AZURITE_HOST is unreachable. The test-infra wiring (the docker
service, the env vars, the start_dependencies entry) landed in a
previous PR; this commit is what makes the suite actually exercise
the Azure driver end to end.

------
AI assisted commit

* Validate Azure timeout and path prefix in Config.IsValid

Parity with the S3-side checks that already cover
AmazonS3RequestTimeoutMilliseconds and AmazonS3PathPrefix. Without
these, a zero/negative AzureRequestTimeoutMilliseconds passes
validation and later creates immediately-expired request contexts,
and leading/trailing whitespace in AzurePathPrefix produces blob
keys that don't match what the admin configured.

Same checks added for the Export* counterparts. The
file_driver.app_error translation is updated to mention the new
'azureblob' option alongside 'local' and 'amazons3'.

------
AI assisted commit

* Stream zip entries from the Azure backend

writeZipEntry was calling ReadFile, which loads the entire blob
into memory before writing it to the archive. For large blobs or
deep directories this spikes RSS or OOMs the goroutine. Switch to
Reader (the streaming azureRangeReader) and io.Copy into the zip
entry so memory stays bounded regardless of blob size.

------
AI assisted commit

* Use a backend-agnostic fallback for FileBackendNoBucketError

The fallback Error() message was "no such bucket", which leaks S3
terminology when an Azure caller returns the type with no wrapped
Err. Use "no such bucket or container" so logs and external error
handling stay neutral across backends.

------
AI assisted commit

* Defend Azure path prefix against directory traversal

Reject ".." in AzurePathPrefix and ExportAzurePathPrefix at config
validation time, since path.Join collapses traversal segments and a
prefix like "../other-tenant" would otherwise escape the configured
isolation boundary.

Harden the prefix helper as a second line of defense: if the joined
path no longer sits inside pathPrefix, fall back to joining the prefix
with the base name of the caller-supplied path. That preserves the
prefix invariant for plugin and import paths that the upload code does
not sanitize uniformly.

------
AI assisted commit

* Honor SkipVerify when constructing the Azure client

FileBackendSettings.SkipVerify is plumbed through from the System Console
the same way it is for S3, so admins toggling the flag for self-signed
endpoints (Azurite, sovereign clouds) get the behavior they expect
without having to drop SSL entirely and send the shared key in clear
text.

------
AI assisted commit

* Warn when the Azure request timeout falls back to its default

Config.IsValid already rejects non-positive AzureRequestTimeoutMilliseconds
for any path that goes through config validation, so this warn only fires
for direct callers that bypass validation (tests, helpers). Logging the
substitution turns a silent coercion into something an operator can
correlate against unexpected request behavior.

------
AI assisted commit

* Cap Azure request timeout at 10 minutes

Reject AzureRequestTimeoutMilliseconds values above the ceiling so an
operator (or someone who has admin access) cannot effectively disable
timeouts by setting the value to math.MaxInt64. A hung Azure call then
holds a goroutine open until the OS gives up.

Applies the same bound to ExportAzureRequestTimeoutMilliseconds. S3 has
the same gap; treating it is out of scope here but worth a follow-up.

------
AI assisted commit

* Refuse AppendFile on blobs without a committed block list

A blob written by another tool (Azure portal, azcopy, a migration script,
a plugin using Put Blob) has its content in the blob but an empty
committed-block list. Committing a new block list against such a blob
silently replaces the existing content with only the appended bytes.

Check the blob's properties before staging when the committed-block list
is empty, and refuse with a clear error if the blob has content. Same
hazard for an admin pointing the backend at an existing container with
pre-existing files.

Adds an integration test against Azurite to lock the behavior in.

------
AI assisted commit

* Surface truncated reads from azureRangeReader

Read closed the body cleanly and returned io.EOF even when the remote
stream terminated before the blob's content length. Callers (and any
retry layer above) then accepted a partial blob as complete.

ReadAt unconditionally rewrote io.ErrUnexpectedEOF to io.EOF, which made
truncated downloads indistinguishable from clean reads. That is exactly
what zip.NewReader consumes for archive readers, so the bulk-import
worker would silently import partial archives.

Read now closes the body, nils it, and returns io.ErrUnexpectedEOF when
EOF arrives before offset reaches size. ReadAt only collapses
ErrUnexpectedEOF to EOF when the full count was delivered and the stream
was consumed to the end of the blob. Otherwise the truncation
propagates with context.

Both code paths are exercised by new fakeDownloader-backed tests.

------
AI assisted commit

* Move container provisioning out of Azure TestConnection

Auto-creating the container inside TestConnection meant a typo in the
System Console (mattermosst instead of mattermost) silently provisioned
an unwanted container in the admin's Azure subscription, with no audit
log and no warning. They'd discover it later when uploads landed
somewhere unexpected.

TestConnection now returns FileBackendNoBucketError when the container
is missing, mirroring the S3 contract. A new MakeContainer method
mirrors S3FileBackend.MakeBucket, and Server.Start dispatches via two
capability interfaces (bucketMaker / containerMaker) instead of a hard
S3 type assertion — so the NoBucket error is no longer silently
swallowed for backends Server.Start has not been taught about.

------
AI assisted commit

* Carry file backend auth detail through to AppError

The Test Connection button collapsed every typed backend failure into
the same generic i18n message. Operators trying to debug bad credentials
or a missing bucket only saw "Unable to authenticate against the file
storage backend" with no SDK code to grep for in their logs.

Use errors.As so the typed checks survive future wrapping, and pass the
underlying error string through the NewAppError details argument. The
AppError serializer surfaces that detail to the admin console alongside
the translated message, so a bad S3 InvalidAccessKeyId or an Azure
AuthenticationFailed shows up in the toast without an i18n schema
change.

------
AI assisted commit

* Remove non-ascii characters from comments

------
AI assisted commit

* Make linter happy

------
AI assisted commit

* Harden Azure prefix boundary check

strings.HasPrefix on the joined path is a string-level check, not a
path-level one, so a configured prefix of "mattermost" accepts a joined
result of "mattermost-evil/...". A crafted caller path like
"../mattermost-evil/secrets" would collapse via path.Join to that exact
sibling and slip through the boundary check, escaping the configured
prefix scope.

Require the joined path to be the cleaned prefix itself or to start with
the prefix followed by a path separator. The fallback path.Join uses the
same cleaned prefix for consistency.

------
AI assisted commit

* Provision Azurite container in standalone test setup

The shared FileBackendTestSuite's SetupTest already handles a missing
container by detecting FileBackendNoBucketError from TestConnection and
calling MakeContainer, but TestAzureFileBackendAppendRefusesNonBlockBlob
bypasses SetupTest and calls TestConnection directly. On a fresh Azurite
instance the test would fail before exercising the append-refusal logic.

Extract a newAzuriteBackend(t) helper alongside azuriteSettings(t) that
builds the backend and ensures the container exists, mirroring the
suite's setup. Use errors.As for forward compatibility with future
wrapping.

------
AI assisted commit

* Fix grammar in email-settings i18n string

"Email settings has unset values." -> "Email settings have unset values."

------
AI assisted commit

* Make Azure MakeContainer idempotent

Treat a ContainerAlreadyExists response as success so that two nodes
racing through TestConnection plus MakeContainer at boot both converge
instead of having the loser fail. Mirrors how the S3 backend handles
the equivalent BucketAlreadyOwnedByYou case.

------
AI assisted commit

* Narrow AzureEndpoint comment to path-style only

The setting only builds path-style URLs, so it cannot reach sovereign
clouds like Azure Government or Azure China, which require vhost-style
endpoints. Update the comment to reflect what the code actually does
and document that sovereign-cloud support is out of scope.

------
AI assisted commit
2026-05-14 16:59:18 +00:00
David Krauser
9f1fe90b69
Migrate CPA to the v2 Property System (#36180) 2026-05-14 12:46:07 -04:00
David Krauser
4aa1c58e37
ci: invalidate poisoned shard-timing cache and guard future saves (#36568) 2026-05-14 16:01:49 +00:00
Julien Tant
323841e9c5
Add board channel types (BO/BP) for Integrated Boards (#35887)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* Add board channel types (BO/BP) with POST /boards API

Introduces board channel types as a new channel variant that reuses the
Channels table but is fully isolated from all /channels endpoints.

Model:
- Add ChannelTypeOpenBoard ("BO") and ChannelTypePrivateBoard ("BP")
- Add IsBoard(), IsOpenBoard(), IsPrivateBoard() helpers
- Add board-specific websocket events (board_created/updated/deleted/restored)

Store:
- SaveBoardChannel: atomic channel + view creation in a single transaction
- Save() rejects board types (forces use of SaveBoardChannel)
- Exclude boards from all channel listing/search queries (GetTeamChannels,
  GetAll, GetChannels, GetChannelsByUser, GetDeleted, autocomplete, search)

API:
- POST /boards: create board channel (feature-flagged behind IntegratedBoards)
- All /channels write endpoints reject board types with 400
- All /channels read endpoints reject or exclude board types
- Open boards get same public-read semantics as open channels

Tests:
- 15 rejection tests covering every /channels write + read endpoint
- 9 exclusion tests covering every listing/search endpoint
- 8 store tests for SaveBoardChannel + Save rejection
- 4 board creation API tests (create, private, flag off, sidebar exclusion)
- 3 authorization tests for board permission semantics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update generated files: i18n, go.mod, migrations list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add i18n translations for board channel error strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix board guard ordering in getChannelMembers and getChannelStats

Move the board rejection check after the permission check so that
nonexistent channel IDs still return 403 (not 404) matching the
original behavior expected by TestGetChannelMembers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Filter boards at store level instead of API guards

Store.Get() now excludes board types via WHERE clause, making boards
invisible to all /channels endpoints. Added GetBoardChannel() for
/boards endpoints. Removed redundant API-level rejectBoardChannel
guards from 10 handlers that already call GetChannel(). Kept explicit
guards only on 3 handlers that don't fetch the channel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix empty i18n translation for app.channel.save_member.app_error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add board system properties, kanban column config, and audit logging

Migration:
- Register "boards" property group with system-wide Assignee (user) and
  Status (select: Todo/In Progress/Complete) fields, both protected
- Idempotent migration following content flagging pattern

Board creation:
- Look up boards fields by name, set board:linked_properties on channel
- Build kanban view props with group_by mapping status options to columns
- Add typed KanbanProps/KanbanColumn/KanbanGroupBy structs with
  ToProps()/KanbanPropsFromProps() for round-tripping
- Add audit record logging for POST /boards
- Add early team_id validation in API handler
- Error on missing status options instead of silent empty columns

Tests:
- Migration test: field creation + idempotent re-run
- Board creation test: verify kanban props + linked_properties
- Fix updateChannelMemberRoles test to use valid role string

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add boards migration mock to testlib store setup

The boards property migration calls System().GetByName() which needs
a matching mock expectation, same pattern as content_flagging_setup_done.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add kanban view props validation and tests

Validate kanban View.Props in IsValid(): group_by required with valid
field_id, 1-100 columns, each column needs id, name, and at least one
option_id. Update all test helpers to produce valid kanban props.

11 dedicated validation tests + round-trip test for KanbanProps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Validate board display name is not empty

Add early DisplayName validation in CreateBoardChannel with a clear
error. Add tests for empty and whitespace-only display names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude boards from GetMany and getChannelsMemberCount

Add board type exclusion to Store.GetMany() and use filtered channel
IDs in getChannelsMemberCount handler so board channels don't leak
into member count results. Add test covering the endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix review issues: drop search indexing for boards, use request context

- Remove search layer indexing of board channels so they stay invisible
  to Elasticsearch/Bleve-powered search and autocomplete
- Replace context.Background() with rctx.Context() for proper
  cancellation and tracing in CreateBoardChannel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix gofmt alignment in websocket_message.go after merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Regenerate server i18n after merge

* Restore translation for permission_policy.app_error

* Filter board channels in name lookups, autocomplete, and indexing

The store-layer board exclusion filter was missing from getByName,
getByNames, GetDeletedByName, the global Autocomplete, and
GetChannelsBatchForIndexing — leaving boards reachable via name
lookups, the no-team-filter search path, and admin reindex jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reject boards in id-batch lookups, unread, and member-mutation endpoints

- GetChannelsByIds, GetChannelsWithTeamDataByIds, and GetChannelUnread
  now exclude BO/BP at the store layer so boards can't slip through if
  callers stop filtering first.
- updateChannelMemberNotifyProps, updateChannelMemberAutotranslation,
  and viewChannel now reject board IDs explicitly via the existing
  rejectBoardChannelByID helper, matching the other write endpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Gate boards properties setup on the IntegratedBoards feature flag

doSetupBoardsProperties registered the boards property group and
fields at every server boot regardless of the IntegratedBoards
feature flag. Skip the migration when the flag is disabled so the
property metadata only appears once boards are actually enabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Use App accessors for boards property lookups

CreateBoardChannel reached into a.Srv().PropertyService() directly
instead of going through the App-level GetPropertyGroup and
GetPropertyFieldByName methods that already wrap the service. Switch
to the standard App accessors so the calls match the rest of the
codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Log full channel input on createBoard audit record

createBoard only captured team_id and type on the audit record, so
failed creations lost most of the request payload. Use
AddEventParameterAuditableToAuditRec with the full channel struct,
matching createChannel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Use allow-list of message channel types in store filters

Inverted every sq.NotEq{[BO, BP]} filter into sq.Eq{messageChannelTypes}
(or teamMessageChannelTypes for queries that also exclude direct
channels) so that any future non-message channel type — wikis, etc. —
is excluded by default rather than requiring every existing call site
to be updated. Also rewrote GetChannelUnread on top of the squirrel
builder so the same allow-list slice can be reused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* go.mod: promote prometheus/common to direct after merge

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Use model.NewPointer for boards property permission field

Drops the local permNone variable in doSetupBoardsProperties and uses
model.NewPointer(model.PermissionLevelNone) inline, matching the
surrounding ContentFlagging/ManagedCategory code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Extract saveViewT to share Views insert between Save and SaveBoardChannel

ViewStore.Save and SaveBoardChannel both built the same INSERT INTO
Views statement, so a future column addition would need updates in two
places. Extract the insert (plus PreSave/IsValid) into a private
saveViewT method that accepts any sqlxExecutor — the regular master
handle for ViewStore.Save, and the channel transaction for
SaveBoardChannel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add IsMessageChannel helper on model.Channel

Mirrors IsBoard for the positive case: returns true for Open, Private,
Direct, and Group channel types. Lets future filtering code be
expressed against the allow-list rather than enumerating board types,
so newly introduced non-message channel types are excluded by default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move board input validation into Channel.IsValidBoard

CreateBoardChannel inlined four guards for type, team, and display
name. Move the type/team_id/display_name checks into a new
Channel.IsValidBoard method so the rules live with the model and
return the AppError directly. The TrimSpace on DisplayName stays at
the call site to match how CreateChannel sanitizes before validating.

Drops the now-unused app.channel.create_board_channel.{invalid_type,
no_team,no_display_name} translations and adds matching
model.channel.is_valid_board.* keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Extract buildBoardKanbanView from CreateBoardChannel

The kanban view construction (read status options, build columns,
serialize props, assemble *model.View) only depends on the status
property field and the creator id. Pulling it into its own helper
shrinks CreateBoardChannel and makes the column-building logic
testable in isolation.

Adds board_test.go with coverage for the empty-options error path,
the standard happy path, and the option-skipping branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Test Channel.IsValidBoard

Cover the four reject cases (wrong type, missing team_id, empty
display name) plus the open and private board accept cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* gofmt board_test.go

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Document POST /api/v4/boards in OpenAPI spec

* Add valid kanban props to api4 makeTestViewForAPI helper

* Assert kanban.ToProps error in makeTestViewForAPI helper

The helper used to swallow the error from kanban.ToProps. Take *testing.T
and require.NoError so a serialization failure surfaces immediately at the
call site instead of producing a malformed view.

* Run boards properties setup unconditionally

The feature-flag gate added in 6298e15e86 made the test suite impossible:
test infra applies FeatureFlags overrides only after app.NewServer returns,
but doAppMigrations runs during NewServer, so the flag was always false at
migration time. The migration short-circuited, the boards property group
was never registered, and every CreateBoardChannel test 500'd with
"boards property group not found."

Drop the gate. The migration is idempotent (keyed in System) and benign —
matches doSetupContentFlaggingProperties and doSetupManagedCategoryProperties.
IntegratedBoards still gates route registration (api4/board.go) and the
CreateBoardChannel runtime entry (app/board.go), so the property group sits
unused until the feature is enabled.

* Add Client4.CreateBoard and use it in tests

Adds boardsRoute() and CreateBoard(ctx, channel) on Client4 mirroring
CreateChannel/CreateView. Refactors api4 board tests off the raw
DoAPIPost("/boards", ...) calls and the SaveBoardChannel store
inserts that predated the API; both now exercise the public client
method, drop the makeTestBoardView helper and the manual SaveMember
follow-ups, and route through setupBoardTest so IntegratedBoards is
enabled where needed.

* make modules-tidy

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-13 16:25:08 -07:00
Joshua D Schoep
d8612e378f
[MM-2541] Shortcut to mark all channels as read for a team (#34012)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
* feat(webapp): added keyboard shortcut for Mark All As Read (MM-2541)

- Added shortcut (within sidebar) for Shift+ESC to mark _all_ messages, teams as read
    - Desktop only
- Added feature toasts for new features and localStorage support
- Added feature toast for mark-all-as-read feature
    - Should decide when/how people want this shown, I just followed designs
    - Will only show if the user has not clicked 'Got it' before, and is not on mobile
- Added confirmation modal for mark all as read shortcut
    - Contains option to not show again, saved in localStorage
- Added English translations for read shortcut
    - Will need i18n aid on other languages

This is a draft version of this feature update that still needs testing and i18n support, along with a11y validation.

* feat(webapp): feature flags and fixes for mark all as read shortcut

- Added feature flags surrounding rollout of mark-all-as-read shortcut
- Added shortcut to list of shortcuts in help section
- Extended tests for new components
- Updated snapshot for sidebar_list, keyboard_shortcuts_modal
- Fixed styling and CSS issues

Still in draft, needs documentation and e2e support.

* fix(webapp): fixed some issues with new mark-all-read feature

- Scoped persistent storage to current user ID
  so that subsequent new logins also get the notification
- Replaced LocalStorage calls with useGlobalState calls, sad
  that I missed that this updated call was being used.
- Fixed an issue that would have caused the new shortcut to
  show up in the Help menu's shortcuts without being enabled.

* Fixed a snapshot test and a missing i18n member

* Replaced useGlobalState with backend-ready usePreference. Previous version was just a mistake as we didnt know about the supported API

* fix(server): fix lint issue with gofmt

* feat(server,webapp): added cleaner and more effective method with which to mark-all-read

- Added 2 new routes to the API (need to find docs to update those):
    - `PUT /api/v4/channels/members/<userId>/direct/read` will mark a user's non-team DMs and GMs as read
    - `PUT /api/v4/users/<userId>/teams/<teamId>/read` will do a similar action as the multi-channel mark_read action, but with a teamId signifier. Because this is using a teamId, it will _not_ handle DMs or GMs.
- Updated sidebar_list.tsx to use these new routes for the new shortcut
- Added extensive testing, including feature flag assurance.

* fix from upstream changes

* fix: eslint errors in teams actions

* document new API endpoints

* fix i18n

* fix err id

* remove unused localhost methods

* use ShortcutKey and ShortcutSequence

* feature_enhancements, mark as read toast enchancements

* read all modal mount point, use openModal

* use handler

* fix style

* fix: fix refactoring typo

* Merge fix: realign branch with upstream changes

Upstream MM-67319/MM-67320 (#36037) moved ShortcutKey and
WithTooltip into the shared package and rewrote the keyboard
shortcuts test to snapshot real DOM instead of a
react-test-renderer tree. The merge resolution missed several
follow-on consequences; clean them up so the branch builds, type
checks, lints, passes i18n-extract-check and runs without
throwing at mount.

- Port the inline-content variant from the deleted channels-side
  shortcut_key.scss to the new shared shortcut_key.css.
- Refresh the keyboard_shortcuts_sequence snapshot so it matches
  Testing Library's container output (DOM only, no component
  nodes, class= not className=).
- Repoint mark_all_as_read_modal and mark_all_as_read_toast at
  components/shortcut_key for ShortcutKeys and use
  ShortcutKeys.escape; the channels-side with_tooltip is now a
  thin re-export and the field was renamed in the shared keys
  map. Without this both consumers threw "Cannot read properties
  of undefined" at mount.
- Switch mark_all_as_read_toast's UserAgent import to
  @mattermost/shared/utils/user_agent; the channels-local
  utils/user_agent path no longer resolves.
- Drop the orphan mark_all_threads_as_read_modal.cancel string
  from en.json so formatjs extraction is in sync.

* Clean up TestReadAllInTeam

Drop four lines left from debugging and replace them with a real
assertion: LastViewedAtTimes must contain the test channel with a
value at or after the most recent post.

Update three client.GetChannel calls to the (ctx, id) signature;
the prior etag argument no longer compiles after upstream removed
it.

* Use SelectBuilder for team channels query

GetTeamChannelsWithUnreadAndMentions built a squirrel query and
then manually called ToSql before handing the string+args to
GetReplica().Select. SelectBuilder accepts the builder directly
and removes the intermediate dance, matching the pattern used
elsewhere in this store.

* Mark all team-channel threads on team read

MarkTeamChannelsAndThreadsViewed used Thread().MarkAllAsReadByTeam
unconditionally, writing every thread membership in the team for
the user even when nothing was stale. Scoping the call to
channelsToView (channels with unread channel-level messages) would
have closed the perf concern but introduced a regression: in CRT
mode a thread reply does not bump the channel's TotalMsgCount, so
a channel can be read at the channel level while still having
unread thread replies, and those would have been silently skipped.

Build the channel-id list from the keys of the times map instead.
GetTeamChannelsWithUnreadAndMentions already populates that map
for every team channel the user belongs to, so no extra query is
needed. MarkAllAsReadByChannels then filters the actual UPDATE
through its LastReplyAt > LastViewed clause, keeping writes
bounded to genuinely stale rows.

Gate the channel-level work (UpdateLastViewedAt, push clearing,
the MultipleChannelsViewed event) on channelsToView being
non-empty, but always run the thread mark and broadcast
ThreadReadChanged for every team channel so CRT clients refresh
thread state in channels that had no channel-level change.

* Mark mark-read audit records as success

The handlers for mark all DM/GM and mark team read created an
audit record with status Fail and never updated it on success,
so successful calls were always logged as failures.

* Mark all DM/GM threads on full read

MarkAllDirectAndGroupMessagesViewed early-returned when no
channel had unreads, so followed threads in DMs/GMs whose
channel-level counters were already current stayed unread under
CRT. Mirror MarkTeamChannelsAndThreadsViewed and call
MarkAllAsReadByChannels for every DM/GM in times.

* Polish DM/GM channels-with-unreads query

Use model.ChannelTypeDirect/Group constants instead of bare
"D"/"G" literals, and update the error wrap to mention DM/GM
channels (it was copied from the team variant).

* Fix stale ReadAllMessages godoc

* Type last_viewed_at_times as int64 map in OpenAPI

The response field was declared as a generic object. Add
additionalProperties so generated clients see it as a
channelId -> int64 timestamp map.

* Gate MarkAllAsReadToast mount on feature flag

The toast was mounted unconditionally, so its async chunk loaded
even when EnableShiftEscapeToMarkAllRead was off. Gate the mount
with the flag so the chunk only loads when the feature is on.

* Return data from markAllInTeamAsRead thunk

Match the {data: response} shape used by adjacent thunks instead
of returning {}, so callers can read the API payload.

* Coerce undefined suffix in createStoredKey

createStoredKey('foo') returned 'fooundefined' when the suffix
arg was omitted. Coerce a missing suffix to ''.

* Refactor mark-read websocket events

* Polish DM/GM channels-with-unreads query

* Fix import order in shortcut_key consumers

* Fix CI

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
2026-05-13 16:38:30 +00:00
Devin Binnie
8a8a4ac8b1
Add Session field to Subject (#36523)
Some checks are pending
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
2026-05-12 20:17:20 +00:00
Pablo Vélez
5661fe1941
MM-68501 - implement GetMaskedVisualAST and wire API handler (#36413)
* MM-68501 - implement GetMaskedVisualAST and wire API handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add missing test and fix style issues

* fix styles

* implement coderabbit feedback

* MM-68501 - PR review: split masking file, model-level access mode, reject contradictory config

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* MM-68501 - apply shared_only filter to non-option field values (binary masking)

* MM-68501 - consolidate masking flag check and log corrupt text value during masking

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-12 18:26:08 +02:00