236 Commits
Author SHA1 Message Date
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00
zeekay 8e84c8d5d6 The plugin API keeps the import path plugins import
Renaming server/public to github.com/hanzoai/team/server/public left the old
path unclaimed, and mattermost-plugin-ai still imports it — so the binary
linked public/model TWICE, ours and upstream's, and public/plugin's
gob.Register calls ran twice under one name:

  panic: gob: registering duplicate types for "[]*model.SlackAttachment":
         []*model.MessageAttachment != []*model.SlackAttachment

at init, before main. The server has not been able to start since that rename
landed, which is why the deployment is still answering from an older build.

server/v8 keeps its new name; nothing outside this repo imports it. public
does not, because its path is not a brand — it is the plugin API, and every
plugin written against Mattermost imports it by that name. Our own agents
fork is proof: it carries a replace pointing that exact path back here.

Reverting the public half is what makes the two copies one again. Measured
after: `go list -deps ./cmd/mattermost` names exactly one public/model, the
binary starts, and it serves.
2026-08-28 20:27:14 -07:00
zeekayandHanzo Dev 316dcf770f The module is ours, so its import path says so
Every package in this fork announced itself as github.com/mattermost/mattermost —
in 4,553 import lines, in both go.mod module declarations, and in the workspace.
A fork whose own import path names another company is one nobody can read as
theirs, and it is the first thing anyone sees at the top of every file.

server/v8 and server/public are now github.com/hanzoai/team/server/…, rewritten
mechanically and verified: `go build ./...` is clean for both modules and `go vet`
passes.

Three things are deliberately UNTOUCHED, because they are other people's names
rather than our branding: mattermost-plugin-ai and mattermost-plugin-mscalendar
are real dependencies we consume, github.com/mattermost/msgpack is a real
dependency, and the github.com/mattermost/mattermost/pull/… links in comments
point at real upstream discussions that renaming would turn into dead links.

server/public needs a `replace` to resolve locally now: the require still names a
version, and no proxy serves our path. The workspace alone does not satisfy a
require, which is exactly how this failed the first time.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-21 09:56:49 -07:00
zeekayandHanzo Dev c85589eb27 serve the API at /v1/workspace
The base moves off /v1/team for two reasons that point the same way.

It collided: /v1/team on hanzo.team is a carve-out routed to the older
cloud service, so the moment this image shipped its own API would have
been answered by something else -- a 404 on our own endpoints, on the
host we serve from.

And workspace is the better noun. It is what the product calls itself
in its own words, and it does not read as though the API belongs to one
team among several.

No /api/ anywhere in a path we own.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 16:03:09 -07:00
zeekayandHanzo Dev c4c2c619ab Serve the HTTP API from /v1/team instead of /api/v4
The product namespace is team, and /api/ is not used as a path prefix
here — API hosts carry the api.* name and paths carry the version. This
moves the one constant every route mounts on, so the public router, the
local router and the websocket endpoint all follow it.

The two 404 catch-alls had the old prefix written out by hand, which
meant the constant was not actually the single source of truth; they now
derive from it, as do the emoji, image-proxy, remote-cluster and
self-ping URLs that were each carrying their own copy of the literal.

The remote-cluster path constants gain a leading slash from the shared
constant, so the two call sites that joined them with an explicit
separator would have produced a double slash and are adjusted.

APIURLSuffixV4 was a bare alias of the value and APIURLSuffixV5 mounted
a subrouter that no route ever registered on, so both are gone rather
than carried forward under names that no longer describe anything.
APIURLSuffixV1 is not ours at all — it addresses the external push
notification service — so it keeps its value and is renamed
PushURLSuffix to stop it reading as a version of this API.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 10:58:01 -07:00
zeekayandHanzo Dev 5979de1824 sign in through Hanzo IAM, and place people by their org
The generic OAuth2 slot upstream calls GitLab is not GitLab-shaped: it is
scope, auth endpoint, token endpoint, user endpoint. Here it is Hanzo
IAM, so it is named that and defaults to hanzo.id's advertised endpoints.
An operator supplies a client id and secret; nothing else.

Three things had to move for that to actually work:

  - The client config only emitted the SSO props under a license, so the
    sign-in button never rendered without one. Hanzo IAM is this
    server's identity provider, not a licensed add-on, so its props are
    unconditional.
  - getSSOProvider rerouted any openid-scoped service to an OpenID
    provider this build does not register, and IAM's scope is
    "openid email profile". One service, one provider.
  - The provider reads sub as the identity. Email and username move;
    sub does not.

Tenancy rides on the owner claim, which IAM treats as authoritative:
each org gets an invite-only team named for it, created on the first
sign-in from that org and joined on every one after. The claim is read
fresh each time, so someone moved between orgs in IAM lands in the right
team on their next login. A profile field cannot influence this -- only
the claim can.

Tests cover the claim mapping and the tenant boundary, including that a
user from one org does not land in another's team. The GitLab provider's
tests are gone with the provider; these replace them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 22:04:59 -07:00
zeekayandHanzo Dev 53c27715ff remove the licensing commerce: seat cap, cloud billing, trial requests
An unlicensed server hard-capped at 250 users (maxUsersHardLimit, no
grace period) and blocked user creation and reactivation at that line.
That is a commercial seat limit, not a safety limit, and a standalone
deploy cannot carry it. GetServerLimits and its two enforcement sites
in user.go are gone.

Also gone: the cloud subsystem that talked to customers.mattermost.com
and portal.internal.prod.cloud.mattermost.com -- billing, subscriptions,
invoices, trial license requests, daily seat reporting, IP filtering,
and the post/file history caps that expressed plan tiers.

Two plugin API methods stay as stubs. GetCloudLimits and
RequestTrialLicense are declared in plugin.API, and plugin binaries are
compiled against those signatures; removing them from the interface
would break a plugin that references either. They report no limits and
no trial.

The post and file accessibility filters stay. They are generic
"hide everything before time T" machinery that reads a stored timestamp,
and with nothing writing that timestamp they return at their first
check. Rewriting 25 read paths to delete an unreachable branch buys
nothing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 21:02:40 -07:00
zeekayandHanzo Dev 109a1c90f7 remove the source-available enterprise tree
server/enterprise/ ships under the Mattermost Source Available License,
which permits production use only under an Enterprise subscription. A
fork that runs standalone cannot carry it, so it goes -- along with the
build plumbing that existed to find it: the enterprise/sourceavailable
build tags, BUILD_ENTERPRISE_READY and its dead ifeq branches, the
EE/Elasticsearch/OpenSearch test targets, and the ldap mock generator.

What is left is the AGPL core, which upstream already calls the team
build (BUILD_TYPE_NAME = team). Prometheus metrics, Elasticsearch and
compliance export went with the tree; database search covers search.

Prepackaged plugins narrow to the four we actually serve: agents, calls,
playbooks, boards.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 20:48:07 -07:00
aa3f1cf9f7 fix(mmctl): lengthen sampledata passwords to meet FIPS minimum (#37867)
* fix(mmctl): lengthen sampledata passwords to meet FIPS minimum

Zero-pad user index to 3 digits in guest and regular-user password
format strings so all generated passwords are >= 14 characters,
matching PasswordFIPSMinimumLength. Update inject-test-data login
hint and add a regression test.

Co-authored-by: Julien Tant <[email protected]>

* Simplify sampledata password length test comment

Co-authored-by: Julien Tant <[email protected]>

* Use subtests in sampledata password length coverage

So a single require failure reports only that case instead of
aborting the rest of the matrix.

Co-authored-by: Julien Tant <[email protected]>

* Name default-user subtests as user for clarity

Co-authored-by: Julien Tant <[email protected]>

* Run sampledata password length subtests in parallel

Co-authored-by: Julien Tant <[email protected]>

* Empty commit to re-trigger CI

---------

Co-authored-by: Julien Tant <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-11 17:28:58 +00:00
71447391a2 [MM-69601] mmctl: support file attachments in post create (#37310)
Co-authored-by: mattermost-code <[email protected]>
Co-authored-by: Doug Lauder <[email protected]>

Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-11 16:21:18 +02:00
32d6add42f [MM-62445] Allow changing a team's name (slug) via mmctl team rename (#37169)
* Allow renaming a team's name (slug) via the team update API

The team update API path is sanitized and intentionally ignores the
Name field, so the previously orphaned RenameTeam app method (which no
longer persisted the Name change) is now wired into the updateTeam
handler and fixed to persist the new slug and emit a team update event.

Co-authored-by: mattermost-code <[email protected]>

* Add --name flag to mmctl team rename

Allow changing a team's name (the URL slug) via mmctl team rename. The
command now accepts --name and/or --display-name, and at least one must
be provided.

Co-authored-by: mattermost-code <[email protected]>

* Polish team rename error message and fix err shadow

Co-authored-by: mattermost-code <[email protected]>

* Strengthen team rename tests

Add regression coverage that the rename path cannot leak non-renamable
fields (email/type), that invalid and duplicate renames leave the slug
unchanged, and a unit case for renaming name and display name together.

Co-authored-by: mattermost-code <[email protected]>

* Address PR feedback: 1 answered, 1 resolved, 0 declined

* Remove unused App.RenameTeam method

The rename path now goes through App.UpdateTeam (via the team update API
and mmctl), leaving App.RenameTeam orphaned. Remove it and update the
name-occupied error source label to UpdateTeam.

* Return non-not-found errors from team name lookup

Only treat store.ErrNotFound from GetByName as an available slug; any
other lookup failure is now returned instead of silently allowing the
rename to proceed. Addresses CodeRabbit review feedback on the team
update path.

* Remove low-value TestUpdateTeamNameLookupError per review feedback

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-10 09:11:17 +02:00
c245713824 Add --active flag to mmctl user list (#37560)
Mirror the existing --inactive filter so admins can list only active
users. Reject using --active and --inactive together, and update unit,
e2e, and docs coverage.

Co-authored-by: Sven Hüster <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-20 15:13:14 +02:00
Harrison HealeyandGitHub d9a3bfb51a MM-69751 Replace concurrent React user setting with feature flag and add to root.html (#37422)
* MM-69751 Replace concurrent React user setting with feature flag and add to root.html

* Address Coderabbit feedback
2026-07-14 14:25:15 -04:00
ae56750319 [MM-69561] Add ability to rotate (regenerate) Personal Access Tokens (#37295)
* MM-69561: Add ability to rotate (regenerate) Personal Access Tokens

- Add UpdateTokenRotate to UserAccessTokenStore interface and implement
  in sqlstore: deletes sessions on the old secret, then updates the
  token row with the new secret and expiry in one transaction
- Regenerate store retrylayer, timerlayer, and mocks
- Add RotateUserAccessToken app method: validates expiry (bot-exempt),
  captures old session for cache eviction, generates new secret, and
  sends a notification email
- Register POST /api/v4/users/tokens/rotate handler with full permission
  checks (create_user_access_token + edit_other_users + manage_system
  for sysadmin targets); rejects OAuth sessions and disabled tokens
- Add RotateUserAccessToken to the Go client (client4.go)
- Add storetest covering secret rotation and old-session cleanup
- Add API4 tests: happy path, permission denials, OAuth rejection, and
  max-lifetime enforcement

* MM-69561: Add dedicated rotate email and i18n strings

- Add SendUserAccessTokenRotatedEmail (subject/body distinct from the
  'added' email so users aren't confused by a rotation event)
- Add SendUserAccessTokenRotatedEmail to ServiceInterface + mock
- Add en.json strings for the rotate email and the two new error ids
  (rotate.app_error, disabled_token.app_error)
- Switch RotateUserAccessToken to call SendUserAccessTokenRotatedEmail

* MM-69561: Add mmctl token rotate subcommand

- Add RotateUserAccessToken to the mmctl Client interface and mock
- Add 'mmctl token rotate <token-id> [--expires-in <duration>]' command
  reusing the existing resolveTokenExpiry/parseExpiresIn helpers from
  'generate'; prints the new secret once on success
- Add unit tests: happy path, --expires-in passed through, server error,
  invalid --expires-in

* MM-69561: Regenerate mmctl docs for token rotate

* MM-69561: Add API docs for POST /users/tokens/rotate

* MM-69561: Fix i18n string ordering after extract

* MM-69561: Add missing API4 test cases for token rotate

Cover the three untested access-control branches flagged by the test
analysis bot:
- Rotating a disabled token returns 400
- Non-system-admin rotating a sysadmin's token returns 403
- Rotating a remote user's token returns 403

* MM-69561: Address review comments

- Fix handler authorization order: check SessionHasPermissionToUserOrBot
  and manage_system before IsRemote/IsActive to avoid leaking token state
  to unauthorized callers; matches revokeUserAccessToken/disableUserAccessToken
- Fix API docs minimum server version: 10.8 -> 10.10

* MM-69561: Restore i18n strings accidentally deleted by extract

The earlier i18n-extract run stripped ~164 unrelated translation keys
(mostly enterprise-only strings like app.pap.* and api.ldap.*) because
the enterprise codebase isn't present in this checkout, so the
extractor treated them as unused. Restore them while keeping the 5
new keys added for token rotation.

* comment

* [MM-69561] Add webapp Regenerate option for Personal Access Tokens

Adds a "Regenerate" link to Account Settings > Security > Personal
Access Tokens that calls the POST /users/tokens/rotate endpoint added
in the server-side rotate PAT work. Regenerating shows a confirmation
modal naming the token, then reveals the new secret via the existing
one-time-copy flow used for token creation.

- webapp Client4.rotateUserAccessToken
- mattermost-redux rotateUserAccessToken action
- Regenerate link/confirm modal/reveal flow in user_access_token_section
- i18n strings
- Playwright e2e coverage

* Fix regenerate PAT e2e test: confirm modal is not nested in the Profile dialog

ConfirmModal renders via react-bootstrap's Modal, which portals to
document.body as a sibling of the Profile dialog rather than a
descendant, so it must be located via #confirmModal on the page
instead of scoped to the Profile dialog locator.

* Let users pick a new expiry when regenerating a Personal Access Token

Previously, regenerating a token always called rotateUserAccessToken
with no expiresAt, so the rotated secret never expired even if the
original token did. The Regenerate confirmation modal now includes the
same expiry picker used by token creation (extracted into a shared
renderExpiryPicker helper), enforces MaximumPersonalAccessTokenLifetimeDays
the same way, and disables the confirm button until a valid expiry is
selected.

* Scope the red background in the Regenerate modal to the warning text only

The confirmation question, expiry picker, and its hints were sitting
inside the same alert-danger box as the warning, making the whole
modal read as an error. Only the warning paragraph keeps the red
background now.

* Move the regenerate confirmation question below the expiry picker

* Align rotate-token wording with UI: use 'regenerate/regenerated'

* [MM-69561] rename pat_expiry_notify job to notify_expiring_access_tokens

Unifies naming with the sibling cleanup_expired_access_tokens job and
fixes the "expiry" vs "expiring" ambiguity: this job warns about tokens
approaching expiry, not ones that have already expired. Safe to rename
outright since the job hasn't shipped yet.

* [MM-69561] remove dead session lookup from EnableUserAccessToken

The GetSessionContext call and its result were never used: both branches
returned nil regardless. Leftover from mirroring DisableUserAccessToken's
shape, which does use its session (to revoke it) unlike Enable.

* [MM-69561] rename remaining PAT identifiers to match AccessToken convention

The job-level rename (pat_expiry_notify -> notify_expiring_access_tokens)
left the app-layer function and its helpers using the old PAT/
PersonalAccessToken naming. Rename them to match:

- NotifyPersonalAccessTokensExpiring -> NotifyExpiringAccessTokens
- patExpiryBucket -> accessTokenExpiryBucket
- sendPATExpiryNotification -> sendAccessTokenExpiryNotification
- patExpiryNotifyBatchLimit -> expiringAccessTokenBatchLimit
- patExpiryThresholds -> expiringAccessTokenThresholds
- maxPersonalAccessTokenExpiry -> maxUserAccessTokenExpiry

Also reword the package doc on notify_expiring_access_tokens, which no
longer needs to explain a PAT/UserAccessToken naming split now that the
app-layer method matches the job name.

Pure rename, no behavior change.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 23:08:19 +02:00
d908576206 mmctl: list and optionally auto-add missing team members on channel move (#37199)
* mmctl: list and optionally auto-add channel members missing from target team on channel move

When moving a channel via 'mmctl channel move', the server rejects the move
if any channel member is not part of the destination team, returning only a
generic error. This adds client-side detection that lists the specific users
that need to be added, and a new --auto-add-users flag that adds those users
to the destination team before moving the channel.

Co-authored-by: mattermost-code <[email protected]>

* mmctl: add unit coverage for --force bypass and member-fetch error paths on channel move

Co-authored-by: mattermost-code <[email protected]>

* mmctl: use membership APIs for channel move preflight

Replace GetUsersInChannel/GetUsersInTeam with GetChannelMembers and
GetTeamMembersByIds so the move preflight matches server MoveChannel
membership checks, including deactivated channel members.

Co-authored-by: mattermost-code <[email protected]>

* mmctl: fix min() lint and expose GetTeamMembersByIds in local API

- Use built-in min() for team member ID batching in channel move preflight
- Register getTeamMembersByIds on local API so mmctl local-mode E2E tests pass

Co-authored-by: mattermost-code <[email protected]>

* mmctl: fix unusedwrite lint in channel move unit tests

Co-authored-by: mattermost-code <[email protected]>

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-07 23:17:29 +02:00
41022c9202 [MM-69599] Add mmctl channel users list command to list channel members (#37311)
* [MM-69599] Add `mmctl channel users list` command

Add a `list` subcommand under `channel users` that prints each channel
member's id, username, email and roles, backed by Client4
GetChannelMembers (paginated) and GetUsersByIds. Mirrors the --page,
--per-page and --all pagination flags used by other list commands.

Co-authored-by: mattermost-code <[email protected]>

* [MM-69599] Expand channel users list test coverage

Add unit coverage for custom --page/--per-page plumbing, multi-page --all
accumulation, the --all fetch error path, members whose user details are
missing from GetUsersByIds, and plain-format template rendering. Strengthen
the e2e --all case to assert the listed member's username, email and roles.

Co-authored-by: mattermost-code <[email protected]>

* Honor --per-page as chunk size when listing all channel users

When --all is set, pass the --per-page flag value to getPages instead of
hardcoding DefaultPageSize. This matches user list and job list behavior
where --per-page controls pagination chunk size and only --page is ignored.

Co-authored-by: mattermost-code <[email protected]>

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-07 09:30:20 +02:00
eb0e00db97 Add job create, show, and cancel commands to mmctl (#37280)
* Add create, show, and cancel subcommands to mmctl job

The `mmctl job` command previously only supported `list` and `update`.
This adds three subcommands backed by existing client methods:

- `job create [type] [--data k=v]` triggers a new job (CreateJob)
- `job show [job]` displays a single job (GetJob)
- `job cancel [job]` requests cancellation via the dedicated cancel
  endpoint (CancelJob), which uses the create-job permission and emits
  its own audit event, unlike `update [job] cancel_requested`

Also fixes the leftover "import jobs" wording in the `list` flag help
and regenerates the mmctl docs.

* Exercise --data flag parsing in mmctl job create test

The create-job test passed the Data map as the flag's default value, so
GetStringToString returned the default without ever exercising pflag's
stringToString parser. Declare the flag empty and Set the value
explicitly so the test validates the real parse path.

* Don't gate mmctl job create on model.AllJobTypes

model.AllJobTypes (which IsValidJobType checks against) is a curated
subset that omits creatable job types such as access_control_sync, so
validating the type client-side wrongly rejected jobs the server can
schedule. Drop the client-side check and let the server validate the
type per its create-job permissions; it returns a clear error for
anything unschedulable. Tab-completion still suggests AllJobTypes as an
advisory hint.

* Remove explanatory AllJobTypes comments from mmctl job

* Add e2e tests for mmctl job create, show, and cancel

Cover the no-permission, not-found, and success paths for the new
generic job subcommands against a running server, mirroring the existing
export job e2e tests.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-06 19:07:08 +02:00
a444cc3c29 [MM-69598] Add mmctl user status command to get and set a user's status (#37312)
* Add mmctl user status get/set commands

Co-authored-by: mattermost-code <[email protected]>

* Register local-mode status routes and add mmctl status tests

Expose GET/PUT /users/{user_id}/status on the local socket API so
mmctl --local can read and set statuses, and add unit + e2e coverage
for the new user status commands.

Co-authored-by: mattermost-code <[email protected]>

* Improve mmctl status tests and validate dnd-end-time earlier

Validate --dnd-end-time before resolving the target user, add command
wiring/flag coverage, a GetMe failure case, and stronger e2e assertions
from the test-quality review.

Co-authored-by: mattermost-code <[email protected]>

* Regenerate mmctl docs for user status command

Co-authored-by: mattermost-code <[email protected]>

* Accept RFC3339 UTC timestamps for mmctl status --dnd-end-time

Use time.RFC3339 parsing so --dnd-end-time accepts Z-suffixed UTC
timestamps in addition to numeric offsets, per CodeRabbit review feedback.

Co-authored-by: mattermost-code <[email protected]>

* Show @username in mmctl user status output

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 12:38:58 +02:00
59f0cdb3d4 [MM-69596] Show user roles in mmctl user search plain-text output (#37309)
Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:22:17 +02:00
fd8c25db20 [MM-69600] Add --show-ids flag to mmctl channel list (#37313)
* Add --show-ids flag to mmctl channel list

Mirror post list --show-ids so the plain-text channel list output
includes channel IDs.

Co-authored-by: mattermost-code <[email protected]>

* Add unit tests for mmctl channel list --show-ids

Co-authored-by: mattermost-code <[email protected]>

* Assert show-ids flag registration in channel list test

Co-authored-by: mattermost-code <[email protected]>

* chore: re-trigger CodeRabbit review after rate limit

Co-authored-by: mattermost-code <[email protected]>

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:21:34 +02:00
Jesse HallamandGitHub 7e0307f65a Fix flaky mmctl plugin e2e tests by using local HTTP server (#37211) 2026-06-30 17:38:39 -04:00
fb7c9b9d7a Add direct message support to mmctl post create (#37159)
* Add direct message support to mmctl post create

Co-authored-by: mattermost-code <[email protected]>

* Regenerate mmctl docs for post create direct messages

Co-authored-by: mattermost-code <[email protected]>

* Strengthen mmctl post create direct message test coverage

Co-authored-by: mattermost-code <[email protected]>

* Document why local mmctl client cannot send direct messages

Add a test comment explaining that local mode lacks the post-create,
direct-channel, and current-user API endpoints required by post create.

Co-authored-by: mattermost-code <[email protected]>

* Add local mode API support for mmctl post create

Implement local mode handlers for POST /posts and POST /channels/direct,
and add --user flag to mmctl post create for specifying the acting user
in local mode instead of relying on GET /users/me.

Co-authored-by: mattermost-code <[email protected]>

* Address CodeRabbit feedback and regenerate mmctl docs

- Defer WriteHeader in localCreatePost until after burn-on-read handling
- Preserve prior viper local state in post create tests
- Regenerate mmctl docs for --user flag

Co-authored-by: mattermost-code <[email protected]>

* Address PR feedback: 2 answered, 3 resolved, 0 declined

* Reset mmctl unit test config state

* Address PR feedback: 3 answered, 1 resolved, 0 declined

* Address PR feedback: 1 answered, 1 resolved, 0 declined

* Retrigger CI after transient Enterprise FIPS proxy.golang.org failure

Co-authored-by: mattermost-code <[email protected]>

* Merge local-mode API tests into their parent test functions

Fold TestLocalCreateDirectChannel into TestCreateDirectChannel and
TestLocalCreatePost into TestCreatePost as local-mode subtests, per
reviewer feedback to consolidate the local-mode coverage with the
existing create tests.

* Revert local mode support for mmctl post create

Reverts 20a2b1c per reviewer feedback: remove the local-mode POST /posts
and POST /channels/direct API handlers, the --user flag, and the associated
audit events. mmctl post create now errors out when run with --local instead
of attempting to create posts in local mode.

* Retrigger CI after transient Enterprise FIPS npm cache failure

The Enterprise CI/tests failure on 8d04b5c was caused by an npm
EEXIST cache error during webapp node_modules install — unrelated to
this PR's mmctl changes.

Co-authored-by: mattermost-code <[email protected]>

---------

Co-authored-by: mattermost-code <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 23:04:07 +02:00
439a9c1825 Move mmctl user deleteall to mmctl system nuke users (#37197)
* Move mmctl user deleteall to system nuke users

Relocate the destructive PermanentDeleteAllUsers command out of the
user namespace into a new system nuke subcommand to reduce the risk of
accidental invocation. Regenerate mmctl docs and move associated unit
and e2e tests accordingly.

Co-authored-by: mattermost-code <[email protected]>

* Address CodeRabbit feedback for system nuke users E2E tests

Exercise SystemNukeUsersCmd with localOnlyPrecheck and --confirm in E2E
tests, verify non-local mode is blocked via subprocess, and update
confirmation text to mention users, posts, and related data.

Co-authored-by: mattermost-code <[email protected]>

* Fix viper local flag pollution in nuke users E2E tests

Avoid setting viper local directly so unit tests that bind the local
flag are not affected when mmctl unit and e2e suites run together.

Co-authored-by: mattermost-code <[email protected]>

* Simplify nuke users E2E test to reuse existing deleteall pattern

Replace the subprocess-based localOnlyPrecheck verification with the
straightforward client-based approach from the original
TestDeleteAllUserCmd. The local-only enforcement is already covered by
the local-only PermanentDeleteAllUsers API endpoint, so the regular and
system-admin clients fail naturally without invoking the precheck. This
also removes the viper local flag manipulation that previously polluted
other E2E tests.

---------

Co-authored-by: mattermost-code <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-25 12:37:26 +02:00
3e85247df8 [MM-69055] Add rank property field type (#36809)
* Add rank property field type and migrate classification field to use it

Introduces a new 'rank' property field type that behaves identically to
'select' across validation, access-control masking, options handling, and
rendering. The classification markings admin panel now creates its
template, system, and channel fields as 'rank' instead of 'select', with
a paired DB migration to flip any existing classification rows.

* Enforce rank validation on ranked property field options

For PropertyFieldTypeRank fields, every option must carry a non-negative,
unique rank. Adds the Rank field to CustomProfileAttributesSelectOption
and strips stray Rank values from options on non-rank field types so
they cannot drift into persisted attrs.

* Materialize rank options in AttributeView and signal field updates to access control

Extend the AttributeView materialized view so rank-typed property values
are exposed as {"name", "rank"} objects, enabling downstream ABAC SQL and
CEL machinery to compare a user's rank against a named option without
baking rank integers into policy expressions.

Add OnPropertyFieldOptionsChanged on the access control service interface
and call it after every property field update so the service can drop any
per-field metadata it caches (such as the rank-by-name map) and invalidate
compiled-policy cache entries that reference the field.

* Show rank-and-lower options/values for shared_only rank fields

shared_only masking on a rank field previously required an exact option
match, the same as select/multiselect. For rank fields the intended
semantics are clearance-style: a caller sees every option and every
target value at or below their own rank.

filterSharedOnlyFieldOptions and filterSharedOnlyValue now branch rank
fields to filterSharedOnlyRankFieldOptions / filterSharedOnlyRankValue,
which compare against the caller's rank instead of intersecting option
IDs. Select, multiselect, and scalar (text/date/user) masking are
unchanged. A caller with no value of their own has no rank and sees
nothing; the source plugin still sees everything.

* Render rank property field type like select in webapp

Treat the new 'rank' custom profile attribute type as a single-select
everywhere it is editable, and make it fully usable from the system
console:

- user_settings/general: render rank fields as a single-select dropdown
  (resolving option IDs to names) instead of a free-text input.
- system_user_detail: resolve rank option names and render the value
  input as a native single-select.
- user_properties_type_menu: add a selectable "Rank" field type
  (reusing the Select icon) so existing rank fields display correctly
  and admins can create new rank fields from the UI.
- en.json: add the "Rank" type label string.

* Make rank cache invalidation cluster-aware and cover field deletion

OnPropertyFieldOptionsChanged previously dropped the access control
service's per-field rank cache only on the node handling the property
field update, so peer nodes kept serving stale name->rank maps until
restart. It was also wired into the update path only, leaving deleted
rank fields cached indefinitely.

Call OnPropertyFieldOptionsChanged from DeletePropertyField so a removed
rank field's cached options are dropped too. The access control service
now broadcasts the invalidation cluster-wide (see companion enterprise
change).

* Backfill option ranks when migrating classification fields to rank

The select->rank conversion previously only flipped the field type. On
upgrades the existing classification options carry no rank (master's
select option struct has no rank field, so the UI-sent rank was dropped
on save), which left an invalid rank field: the matview projected null
ranks, shared_only masking hid every value, and the validation hook
rejected any later edit.

Backfill a rank onto each option from its 1-based array position. The
classification UI keeps levels in severity order and rewrites the full
options array on every save, so position is the authoritative ordering;
1-based matches both the UI's `opt.rank ?? (i+1)` fallback and the
presets' ranks, so a configured preset is still recognized after upgrade.
The flip and backfill share one migration (one transaction), so the field
is never observable in the invalid (rank, null-rank) state, and the down
migration strips the rank key to restore the prior select shape.

Only the three known classification fields with a non-empty options array
are touched; the updates match at most three rows and add no meaningful
lock footprint.

* Add System Console UI for ranked property fields

Implements the admin UI for the ranked property field type across three
System Console surfaces:

- User Attributes: the "Ranked" attribute type, numbered value chips in
  ascending rank order, a per-chip popover (rename / change rank / remove),
  and an "Edit ranking" modal with drag-reorder, arrow steppers, numeric
  rank inputs, auto-assigned next rank, and inline duplicate rejection.
- Membership Policy editor: ranked comparison operators (is exactly,
  is not, is at least, is greater than, is at most, is less than) shown for
  ranked attributes in place of the standard set, with CEL build/parse and
  default-operator wiring.
- User detail: a ranked-value picker rendering options highest-rank-first
  with numbered badges and a checkmark on the assigned value.

Adds Playwright e2e coverage for all three surfaces (plus page-object
helpers) and webapp unit tests for the operator wiring and rank utilities.

* Fix enterprise CI: regenerate access-control mocks and add rank i18n key

The enterprise CI lane fails on two pre-existing backend gaps on this
branch:

- go vet (check-style): OnPropertyFieldOptionsChanged was added to
  PolicyAdministrationPointInterface but the generated einterfaces mocks
  were never regenerated, so AccessControlServiceInterface /
  PolicyAdministrationPointInterface mocks no longer satisfy the
  interface (access_control_test.go, access_control_masking_test.go).
  Add the missing method to both mocks.

- Check i18n: enterprise access_control/administration.go references the
  app.pap.rehydrate_rank.app_error key, which was never added to
  server/i18n/en.json. Add it so `make i18n-extract` produces a clean
  diff.

Verified: both affected test packages compile, `make i18n-extract`
yields no diff, and `make i18n-check` passes.

* Retrigger enterprise CI

Pick up enterprise fix 15914c11 (require.NoError in rank cel_utils tests)
in the combined Enterprise CI/tests lane, which pins the enterprise SHA at
mattermost-side dispatch time.

* Retrigger enterprise CI

Pick up enterprise fix 48db14db (golangci-lint findings in rank
access-control code) in the combined Enterprise CI/tests lane.

* Clamp shared_only rank values to the caller's rank instead of hiding

For a shared_only rank field, filterSharedOnlyRankValue previously hid a
target's value entirely when the target outranked the caller, returning
nil. That answered "is the target at or below me?" but not the question
the field exists for: "what can we talk about, and at what level?" A
higher-ranked target simply disappeared.

Now the value is clamped to the highest rank the caller shares with the
target — the target's own value when it is at or below the caller's rank,
otherwise the option at the caller's own rank. The caller always learns
their shared ceiling and never sees a rank above their own. Ranks are
unique per field, so the clamp target is unambiguous.

* Renumber rank migrations to 194-197 (after property_groups 193)

000193_add_property_groups_schema_version was already on master, so
our rank migrations should follow it: 194-197.

* Fix e2e classification helper field names after rename migration

Migration 000196 renamed channel_classification → classification and
system_classification → classification. Update the e2e helpers to
create and clean up fields with the new canonical names so the frontend
(which looks for 'classification') can find them.

* Remove unused i18n key and fix e2e prettier formatting

app.pap.save_policy.advanced_expression_blocked was added to en.json
but never used as an AppError key in Go code — remove it so i18n-check
passes. Also fix prettier formatting in ranked_operators.spec.ts.

* Fix 000194 down migration: drop AttributeView before enum rebuild

TestUpAndDownMigrations/Should_be_reversible_for_postgres failed because
the down migration rebuilds the property_field_type enum (Postgres can't
drop an enum value in place), which requires ALTER COLUMN on
PropertyFields.Type. The AttributeView materialized view reads that
column, so Postgres rejects the alter: "cannot alter type of a column
used by a view or rule".

The up path is unaffected because it uses ADD VALUE (no column rewrite);
only the down rebuild trips the dependency. Mirror the canonical
drop-view / alter-column / recreate-view pattern: drop AttributeView,
rebuild the enum, then recreate the no-rank view (the same definition
000197's down restores, which 000177's down later replaces).

Verified end-to-end against PostgreSQL 16: full up-then-down sequence
now passes.

* Add unit tests for rank validation and field-change signalling

Fills two gaps flagged by PR test analysis:
- access_control_attribute_validation_test.go: rank option validation
  (valid ranks persist; missing/negative/duplicate rank rejected; zero
  rank allowed; non-rank fields strip stray rank values).
- property_field_test.go: UpdatePropertyFields/DeletePropertyField
  signal the access control service via OnPropertyFieldOptionsChanged
  for each affected field, and stay nil-safe when no AC service exists.

* Add api4 tests for rank-field permission branching

Fills the API gap flagged by PR test analysis: rank fields participate
in the isOptionsOnly permission branch alongside select/multiselect.
- properties_test.go: an options-only PATCH on a rank field uses the
  narrower manage-options permission (member succeeds), while a
  structural PATCH (name change) requires the full edit-field
  permission (member forbidden, admin succeeds).
- custom_profile_attributes_test.go: an options-only PATCH on a rank
  CPA field routes through the options path and round-trips ranks.

* Add mmctl unit tests for rank attribute resolution

Fills the mmctl gap flagged by PR test analysis. Both resolution
directions for rank-typed attributes were untested:
- user_attributes_test.go (TestResolveDisplayValue): a stored rank
  option ID resolves to its option name for display; unknown IDs and
  option-less fields fall back to the raw value.
- user_attributes_value_test.go (TestResolveOptionNamesToIDs): setting
  a rank value by option name resolves to the option ID; already-an-ID
  and unknown names pass through.

* Refine ranked attribute editing UI

Edit ranking modal:
- Derive ranks from row position so reordering or removing a value always
  keeps them contiguous (1..N) with no gaps or duplicates
- Drop the arrow steppers in favor of drag-only reordering; render the drag
  clone through a body portal so it isn't offset by the modal dialog transform
- Show values as read-only chips (lowest-first) with Lowest/Highest labels
- "Add value" toggles into an inline row (fake handle + borderless field);
  Enter commits the value instead of closing the modal
- Title shows the field name plus "Ranked attribute"

Inline rank values:
- Rank badge fills the left of the chip; add an inline remove (X) that reuses
  react-select's CrossIcon so it matches select fields

Also give the rank attribute type its own SortAscendingIcon and trim a couple
of stale comments.

* Fix SCSS property order lint errors

* Fix e2e rank modal tests to match drag-and-drop UX

The ranked schema modal uses position-based ranks (drag to reorder) — there
are no editable numeric rank inputs per row and no per-row name inputs.  The
two failing tests were written against an earlier design that had those inputs:

- "rejects a duplicate rank inline" expected .ranked-schema-modal__rank-input
  and .ranked-schema-modal__error (neither exists).  Replaced with a test that
  covers the actual duplicate-label guard on the add-value inline input.

- "adds a value via the Edit ranking modal" expected .ranked-schema-modal__name-input
  (doesn't exist) and that save is disabled while the add input is open (it
  isn't — save is disabled only when rows is empty).  Updated to use the real
  add-value flow: click "Add value", fill .ranked-schema-modal__add-input, blur
  to commit.

Also removes the three broken page-object helpers (rankedModalRankInputs,
rankedModalError, rankedModalNameInputs) whose CSS selectors never existed.

* Enforce positive ranks and backfill them on select-to-rank conversion

Rank values must be positive integers (>= 1), matching the webapp's
isValidRank helper and rankForIndex which both require rank >= 1. The
server was accepting rank = 0 (rank < 0 check), allowing invalid data
that would cause filtering bugs: a caller with a rank-0 option would
see nothing in a shared-only field since no option would satisfy
rank <= 0. Change the guard to rank <= 0 and update the error message
and tests accordingly.

When converting a field from select/multiselect to rank via the type
dropdown, the existing options had no ranks. The server's option
validation then rejected the save (or the UI showed stale local state
appearing valid). Auto-assign contiguous 1-based ranks in array order
at the point of type change so the conversion is immediately valid and
the user does not need to open the rank-ordering modal just to commit.

* Show an inline error when a ranked option is renamed to a duplicate

Renaming a ranked option to a name already used by another option was
silently ignored: the draft reverted with no feedback, leaving the user
unsure why their edit didn't stick. The add-value flow already warns on
duplicates; the rename path now matches it.

The chip popover surfaces "Values must be unique." beneath the label
input while the typed name collides, and the rename stays blocked so a
duplicate is never committed.

* Scroll the ranked schema list so the modal footer stays in view

A ranked attribute with many values (e.g. 30) grew the edit modal taller
than the viewport, pushing the Save/Cancel footer off the bottom of the
screen where it couldn't be reached.

Cap the value list at 50vh and scroll it internally, keeping the header
and footer anchored. The list is the droppable's own scroll container, so
react-beautiful-dnd auto-scrolls it while a row is being dragged, and a
small right padding keeps the scrollbar clear of the remove buttons.

* Add a shared allow-list for option-bearing property field types

The "is this a select/multiselect/rank field?" check was open-coded as a
three-way type-negation chain in the model, both PSAv2 patch handlers, and
the access-control shared-options filter. Each copy had to be kept in sync
by hand as field types were added.

Introduce an optionFieldTypes allow-list in the model with a
PropertyFieldType.SupportsOptions() helper (mirroring the webapp's
supportsOptions) and route every call site through it, so adding a future
option-bearing type is a one-line change.

* Guard the rank options filter before its store lookup

filterSharedOnlyRankFieldOptions built the option-rank map and ran the
caller-rank store lookup before checking whether the field had a usable
options array at all. A field with no options (or a malformed attrs blob)
has nothing to filter, yet still paid for a database query.

Move the cheap nil/shape guards to the top so an optionless field returns
immediately, and reuse the extracted options slice in the filter loop
instead of pulling it out of attrs a second time.

* Memoize the ranked attribute editors' event handlers

The rank schema modal and the inline rank-values cell recreated every
handler (drag-end, move, remove, confirm, rename, add) on each render.
These are passed down to the modal footer, the drag-and-drop context, and
per-chip popovers, so the fresh identities defeated memoization downstream.

Wrap them in useCallback with explicit dependencies. No behavior change.

* Add tests for the classification select-to-rank migration

The 000195 conversion is non-trivial: it flips specific (Name, ObjectType)
fields in the access_control group from select to rank, then backfills a
1-based rank onto each option from its array position, guarding against
empty or absent options arrays.

Cover the behavior end to end against a real Postgres schema: the type
flip with position-derived ranks in order, the empty- and absent-options
guards (type flips without fabricating options), the name/object-type/group
mismatches that must stay select, and the down round-trip that strips the
ranks and reverts the type.

* Gate the rank property field type behind a feature flag

Add a PropertyFieldRank feature flag (off by default) that gates the
"rank" custom profile attribute type.

The enforcement is a single app-layer gate, rankPropertyFieldGate, shared
by both CreatePropertyField (blocks creating a rank field) and
UpdatePropertyFields (blocks converting an existing field to rank). When
the flag is off it returns app.property_field.rank_disabled.app_error.

The admin console CPA type menu hides the rank option unless the flag is
on, read via useGetFeatureFlagValue.

Existing app/api4 tests that exercise rank fields now enable the flag in
their setup so they continue to pass.

* Scope the rank feature-flag gate to user-object fields

The PropertyFieldRank gate rejected any rank-typed field create/update
when the flag was off. But classification markings now uses the rank
type internally (template/system/channel object types) and ships GA
behind the separate ClassificationMarkings flag, which is on by default
while PropertyFieldRank is off by default. The migration also converts
existing classification fields to rank unconditionally. Together this
broke the classification admin panel in the default configuration: both
creating new classification fields and editing existing (migrated) ones
returned app.property_field.rank_disabled.app_error.

Scope the gate to ObjectType == user, which is the only origin of the
user-facing rank CPA type (createCPAField forces ObjectType=user). Rank
fields on other object types are exempt, so classification keeps working
regardless of the flag while the user-facing CPA rank type stays gated.

Add regression cases proving a non-user (classification-style) rank
field is creatable and convertible-to with the flag off, and enable
PropertyFieldRank in the e2e default config so the user-facing rank
specs pass.

* Update webapp/channels/src/components/admin_console/system_properties/rank_badge.scss

Co-authored-by: abhijit-singh <[email protected]>

* Enable the rank feature flag in the e2e CI server env

The new rank e2e tests failed in playwright-full because the CI docker
server gets feature flags from MM_FEATUREFLAGS_* env vars (config patches
don't stick without a SplitKey), and PropertyFieldRank was only set in the
local default_config.ts. Add the matching env var so the flag is on in CI.

* Fix stale migration-number references in classification rank down migrations

The 000195 and 000196 down migrations carried comment references to their
development-era numbers (000191/000192). Correct them to the current numbering
(000195 reverses itself; 000196's down restores distinct names before 000195's
down matches on them). Addresses review feedback from @mgdelacroix.

* Use PropertyFieldType.SupportsOptions() for option-field type checks

Replace the repeated `Select || Multiselect || Rank` comparisons with the
existing SupportsOptions() helper across the access-control masking, validation,
and option-filtering paths, and in mmctl value resolution. Behavior is unchanged
(SupportsOptions covers exactly those three types). Addresses review feedback
from @mgdelacroix.

* webapp: use supportsOptions() helper for option-field type checks

Replace the repeated `type === 'select' || 'multiselect' || 'rank'` checks with
the existing supportsOptions() helper from @mattermost/types/properties, matching
the server-side SupportsOptions() usage. Addresses review feedback from @mgdelacroix.

* Include rank in extractOptionIDsFromValue error message

The function handles select, multiselect and rank, but the error message only
listed select and multiselect. Addresses review feedback from @mgdelacroix.

* Validate option rank in CustomProfileAttributesSelectOption.IsValid

When an option carries a rank, enforce that it is a positive integer at the
model layer, mirroring the field-level option validation. Rank stays optional
so select/multiselect options (which carry none) remain valid. Addresses review
feedback from @mgdelacroix.

* Invalidate access-control cache before broadcasting field update

In UpdatePropertyFields, move the OnPropertyFieldOptionsChanged notification
above the websocket broadcast so a client reacting to the update event never
re-reads stale cached field metadata. This matches the ordering already used in
DeletePropertyField. Addresses review feedback from @mgdelacroix.

* Assert stored options in options-only patch permission tests

The select/multiselect/rank options-only update tests only checked that the
request succeeded; also assert the returned field's options (id, name, and rank
for the rank case) so the tests actually verify the change was persisted.
Addresses review feedback from @mgdelacroix.

* Add FeatureFlagPropertyFieldRank to webapp ClientConfig type

The server-side PropertyFieldRank feature flag and user_properties_type_menu.test.tsx
reference FeatureFlagPropertyFieldRank, but it was never added to the webapp
ClientConfig FeatureFlags type, leaving tsc -b red. Add the missing field.

* Tighten the ranked option popover spacing

Drop MUI's default 8px MuiList top/bottom padding on the per-option popover and
reduce the label input's bottom padding to 4px, so the popover reads tighter
without jamming the label input against the top edge. Addresses design feedback
from @abhijit-singh.

* Add header and footer dividers to the Edit ranking modal

Enable GenericModal's bodyDivider and footerDivider so the ranked schema modal
shows a divider under the header and above the Save/Cancel footer, which also
delineates the scroll area when the value list is long. Addresses design
feedback from @abhijit-singh.

* Match ranked 'Add values…' affordance to select/multiselect

The ranked values cell now mirrors the select/multiselect CreatableSelect
cell for consistency across field types:

- Tab commits a pending value (keeping focus in the input for the next
  one) when it's non-empty and not a duplicate, matching the select cell;
  a blank/duplicate input lets Tab move focus away normally.
- The empty-state placeholder uses the shared 'Add values… (required)'
  text and matches react-select's placeholder color (full-opacity
  neutral50) and 10px content inset, so size, color, and left alignment
  line up with the select cell.
- The placeholder is hidden once values exist, mirroring react-select.
- The chips well gets the same hover/focus background tint and text
  cursor as the select control.

* Auto-size the ranked 'Add values…' input like react-select

The bare <input> scrolled its overflow in a fixed-width box, while the
select/multiselect cell grows its input and wraps to a new row as you
type. Replicate react-select's sizer technique: wrap the input in an
inline-grid whose hidden ::after mirrors the live text (content:
attr(data-value), white-space: pre, font: inherit) and sizes the grid
column the input fills. The input now grows with its content and, as a
flex child of the values well, wraps to a new row when it no longer fits.

Both the input and the sizer use 'font: inherit' (matching react-select)
so the measured and rendered text line up and the font matches the select
cell. Since the auto-sized input no longer spans the whole cell, a
mousedown handler on the well forwards focus to the input, mirroring
react-select's clickable control.

* Fix consistent-type-imports lint errors in Playwright e2e specs

* Enable PropertyFieldRank feature flag in rank e2e test setup

The Rank type option in the attribute type menu is gated behind the
PropertyFieldRank feature flag. initSetup resets config, so the flag
must be explicitly re-enabled before the test exercises the UI selector.

* Remove broken feature-flag patchConfig from rank e2e setup

setupTest called patchConfig({FeatureFlags: {PropertyFieldRank: 'true'}})
with the string 'true'. PropertyFieldRank is a Go bool, so the server's
patchConfig handler (which does json.Decode(&cfg) into *model.Config before
any filtering) failed to unmarshal the string into a bool and returned 400
"Invalid or missing config in request body." This hard-failed setupTest and
all four specs (CI run 27387310039: 4/4 failed at spec line 36).

The call was both broken and unnecessary:

- Without a SplitKey the config store marks FeatureFlags read-only
  (PlatformService.SetupFeatureFlags -> SetReadOnlyFF(!splitConfigured)), so
  no patchConfig can toggle a flag. The flag is enabled at the server level:
  the MM_FEATUREFLAGS_PROPERTYFIELDRANK env var in CI (added to
  e2e-tests/.ci/server.generate.sh) and the server config locally.
- With the env var in place, all four specs passed at commit 8fca57bf3b; the
  later commit that added this line is what regressed them.

Remove the call and document where the flag actually comes from.

* Locate ranked add-value input by class, not placeholder

The "creates a ranked attribute" spec timed out at addRankValuesToLast
waiting for getByPlaceholder('Add value…') on the inline add-value input.

Commit 3c12f30008 ("Match ranked 'Add values…' affordance to
select/multiselect") changed that input in two ways the page object never
caught up with:

- the placeholder text became 'Add values… (required)' (plural, suffixed),
  so 'Add value…' no longer matches; and
- the placeholder now renders only in the empty state
  (placeholder={showPlaceholder ? ... : undefined}), so it disappears after
  the first value is added — a placeholder lookup can never add 3 values.

Locate the input by its stable class .user-property-rank-values__add-input
instead, which is present regardless of options count or placeholder text.

This is the real failure the reverted patchConfig change had misdiagnosed as
a feature-flag issue. The spec passed at 8fca57bf3b because that predates
3c12f30008 (placeholder was then 'Add value…' and always shown).

* Empty commit to retrigger CI after enterprise branch update

Co-authored-by: Maria A Nunez <[email protected]>

* Fix migration test after renumbering 000194 to 000198

During conflict resolution, migration 000194_add_type_id_index_to_access_control_policies
was renumbered to 000198 to maintain sequential ordering after the new rank migrations.
This commit updates the corresponding test file to match:
- Renamed migration_000194_test.go to migration_000198_test.go
- Updated test function name from TestMigration000194 to TestMigration000198
- Updated migration file references to point to 000198 instead of 000194

Co-authored-by: Maria A Nunez <[email protected]>

* Renumber migrations: restore 000194 from master, move rank migrations to 000196-000199

The 000194_add_type_id_index_to_access_control_policies migration came from master and should keep its original number. Our rank migrations have been renumbered:
- 000194 -> 000196: add_rank_to_property_field_type
- 000195 -> 000197: convert_classification_fields_to_rank
- 000196 -> 000198: rename_classification_linked_fields
- 000197 -> 000199: add_rank_to_attribute_view

This leaves 000195 available for the threadmemberships_cleanup_v2 migration from master.

Co-authored-by: Maria A Nunez <[email protected]>

* Rename migration test files to match renumbered migrations

After renumbering rank migrations from 000194-000197 to 000196-000199, the test file migration_000195_test.go (which tests the classification->rank conversion) needed to be renamed to migration_000197_test.go to match the new migration number. Updated the test function name and migration file references accordingly.

Co-authored-by: Maria A Nunez <[email protected]>

* Filter policy field autocomplete to user-type fields only

Add ObjectType: user filter to GetAccessControlFieldsAutocomplete so
the attribute picker in the Membership Policies editor no longer
surfaces template, system, and channel classification-marking fields
as ghost entries alongside real user CPAs.

MM-69366

---------

Co-authored-by: abhijit-singh <[email protected]>

Co-authored-by: Maria A Nunez <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-18 16:43:51 -04:00
Harshil SharmaandGitHub 97d5c39f9a Added pre-migration infra and a pre-migration for fixing schema ID migration numbers (#36870)
* Added automated fix for https://github.com/mattermost/mattermost/pull/36866

* Added log:

* demoved debugging markers

* Added tests

* Review fixes

* Updated tableExists function to only look in the current schema

* Review fixes

* Broken down a long commne tline into shorter ones

* Added premigration in CLI upgrade command

* Code simplicity

* CI

* CI

* CI
2026-06-11 17:09:43 +05:30
Alejandro García MontoroandGitHub 5e11ed1cd9 MM-69175: Fix broken CI steps (#36989)
* Fix broken migration/codegen CI self-checks

The git status self-checks in server-ci.yml became silent no-ops once
these jobs moved into the build container (#33679): the resolved shell
is sh, where the bash-only [[ ]] errors out, and git rejects the
checkout with "dubious ownership". Switch to POSIX [ ], mark the
workspace safe so git status runs, and print the diff on failure across
all affected checks.

* Regenerate stale migrations.list

The broken check-migrations step let an outdated migrations.list ship.
Regenerate it with make migrations-extract to add migration 000193.

* make mocks

* make gen-serialized

* make mmctl-docs
2026-06-10 13:54:10 +02:00
bbc9b152e6 MM-68417, MM-68420: API support for PAT expiry and admin policy settings (#36706)
* MM-68417, MM-68420: API support for PAT expiry and admin policy settings

POST /users/{id}/tokens now accepts a client-supplied expires_at
(previously stripped per the TODO in api4/user.go), and the create app
method enforces two new ServiceSettings:

  - EnforcePersonalAccessTokenExpiry (bool, default false): when on,
    rejects creates with expires_at == 0
  - MaximumPersonalAccessTokenLifetimeDays (int, default 0 = unlimited):
    caps how far in the future expires_at may be

Rejections return distinct app error ids so clients can disambiguate:
expires_at_required, expires_at_in_past, expires_at_too_far.

GET /users/{id}/tokens already serializes expires_at via the model's
JSON tag added in MM-68419; clients derive token status (active /
expired / inactive) from is_active + expires_at without a separate
server-side field, keeping the response shape minimal.

The Client4 helper CreateUserAccessToken gained an optional variadic
expiresAt parameter (and the mmctl Client interface + mock match)
rather than introducing a parallel WithExpiry method.

Refs: MM-68417, MM-68420

* MM-68417: exempt bot accounts from PAT expiry enforcement

Mirrors the existing EnableUserAccessTokens bypass at session.go:453,
where bot tokens are allowed even when human PATs are disabled. Bots
are programmatic clients that typically need long-lived credentials,
and integrations that provision them would otherwise break the moment
an admin enables EnforcePersonalAccessTokenExpiry — turning a settings
toggle into a footgun. The expiry policy now applies only to human
users; bots can still be given a future expires_at by callers that
want it, but the server won't require one.

Locked in by a new TestCreateUserAccessToken/bot_tokens_are_exempt
subtest that enables enforcement plus a 30-day cap, creates a bot,
and asserts a non-expiring token is accepted.

Refs: MM-68417

* MM-68420: bound MaximumPersonalAccessTokenLifetimeDays in isValid

Negative values silently meant "unlimited" (the runtime check is `> 0`),
and very large values overflow int64 when computing
`now + days*86_400_000` at token-creation time, producing a wrap-around
that either rejects all reasonable expiries or accepts past timestamps
as valid.

Bound the setting in ServiceSettings.isValid to [0, MaxPersonalAccess
TokenLifetimeDays] where the cap is 36500 (100 years) — past any
realistic operational use and well clear of int64 overflow. Surfaces
as a config validation error rather than a silently-broken runtime
check. New TestServiceSettingsIsValid cases lock in zero, negative,
upper-bound, and above-upper-bound behavior.

Refs: MM-68420

* MM-68417: reject multiple expiresAt values in Client4 helper

CodeRabbit caught that the variadic CreateUserAccessToken silently used
expiresAt[0] when callers passed more than one value, masking a
mis-call instead of failing fast. Return an error in that case so the
misuse is visible at the call site rather than producing a token with
the wrong (or right-but-coincidental) expiry.

Refs: MM-68417

* MM-68417: make expiresAt a required parameter on Client4.CreateUserAccessToken

Drop the variadic in favor of a regular int64 parameter. The variadic
form silently dropped extra values and made a misuse undetectable at
the call site (per CodeRabbit review on PR 36706); the previous fix
guarded against >1 values at runtime, but a required parameter is
strictly better — the compiler now refuses the misuse and every caller
is forced to make a deliberate decision about expiry. Existing callers
that want the old behavior pass 0 (== never expires).

Refs: MM-68417

* MM-68417: add --expires-in flag to mmctl user token generate

Adds an --expires-in duration flag so operators can create expiring
PATs via the CLI. Accepts the standard Go duration syntax plus a
trailing 'd' for days (the common case for token lifetimes), e.g.
--expires-in 90d, --expires-in 12h, --expires-in 1h30m. Empty (the
default) means no expiry — matching prior behavior. Without this
flag the command was unusable once an admin enables
EnforcePersonalAccessTokenExpiry, since every create would fail with
app.user_access_token.expires_at_required.app_error.

Flag parsing now happens before the user-lookup API call so the
command fails fast on invalid input. Regenerated mmctl docs reflect
the new flag.

Refs: MM-68417

* MM-68417: cap --expires-in day count to prevent time.Duration overflow

parseExpiresIn returns time.Duration(days) * 24 * time.Hour, which is
int64 nanoseconds and overflows past ~106751 days (CodeRabbit caught
this). Cap at model.MaxPersonalAccessTokenLifetimeDays (36500) so the
CLI rejects values the server would reject anyway, well below the
int64-overflow point. Adds two test cases (at-cap and beyond-cap) to
TestParseExpiresIn.

Refs: MM-68417

* MM-68420: collapse PAT expiry settings into a single max-lifetime setting

Remove ServiceSettings.EnforcePersonalAccessTokenExpiry and fold the
policy onto MaximumPersonalAccessTokenLifetimeDays: 0 means no policy
(never-expiring tokens allowed, no cap), while a value > 0 requires every
new token to expire within that many days. The two-setting design let an
admin set a maximum but leave enforcement off, silently allowing
never-expiring tokens to sidestep the cap; the only combination the
boolean added (require expiry, no upper bound) has little practical
value. The removed field was introduced on this branch and never
released, so this is not a breaking change.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-04 14:21:35 +02:00
8d20e78741 MM-68664: Microsoft Entra ID / Default Credential authentication for Azure Blob Storage (#36733)
* Add DefaultAzureCredential authentication for Azure Blob Storage

Adds a second authentication mode for the Azure filestore backend
alongside the existing shared-key path. The new "default credential"
mode constructs azidentity.NewDefaultAzureCredential, which discovers
managed identity, workload identity, service principal env vars, and
az login in that order at runtime - the standard Microsoft pattern for
host-provided identities.

The credential type is configured via FileSettings.AzureAuthMode (and
ExportAzureAuthMode for the dedicated export store). Both default to
shared_key so existing deployments are unaffected. The access-key field
is only required under shared_key; default_credential reads identity
from the host environment and needs no per-mode config.

------
AI assisted commit

* Add Azure authentication selector to the System Console

Adds an "Azure Authentication" dropdown to both the primary file-storage
panel and the dedicated export-store panel. Two options: "Shared key"
(the existing default) and "Default credential (Microsoft Entra ID)".
The Azure Storage Account Key field is hidden when default credential
is selected; it has no role in that auth mode.

The Cypress spec is extended to cover the new dropdown's visibility
toggling.

------
AI assisted commit

* Use fmt.Errorf instead of pkg/errors

* Do not support empty AzureAuthModeSharedKey

There is no need to support legacy settings when a feature is not yet
released.

* Bring in master's Azure Blob Storage Cypress spec and scroll the access key into view

Two related changes:

- The merge commit just before this one missed master's MM-68787 updates
  to the Azure Blob Storage Cypress spec (the AzureClouddropdown
  visibility, the disabled -> not.exist tightening when S3 driver is
  selected, and the new "shows the custom endpoint only for the Custom
  cloud" test). This commit pulls those in.
- The new "hides the access key when the authentication mode is default
  credential" spec asserts the access key field is visible immediately
  after selecting the Azure driver. With the AzureAuthMode and (now
  landed) AzureCloud dropdowns above it, the field sits below the
  visible area of the System Console scroll container, and Cypress's
  strict be.visible check fails on overflow clipping. scrollIntoView
  mirrors what the Test Connection spec already does for the same
  reason.

------
AI assisted commit

---------

Co-authored-by: Mattermost Build <[email protected]>
2026-05-27 10:57:26 +02:00
71cad2e855 MM-68787: Support sovereign-cloud endpoints for Azure Blob Storage (#36732)
* Support sovereign-cloud endpoints in the Azure Blob Storage backend

Adds an AzureCloud enum to FileBackendSettings and the matching
FileSettings / ExportFileSettings entries, defaulting to commercial.
The supported values pick the service URL the SDK signs against:

  - commercial: vhost-style against blob.core.windows.net, exactly
    the existing default. Only the storage account name is needed.
  - government: vhost-style against blob.core.usgovcloudapi.net, no
    user-supplied endpoint. Only the storage account name is needed.
  - custom: the admin-provided AzureEndpoint is the full service URL,
    scheme and storage account included, and Mattermost passes it to
    the SDK unchanged. Covers Azurite, reverse proxies, Azure China,
    and any future Azure cloud Mattermost has not codified a preset
    for.

Wires the new field through NewFileBackendSettingsFromConfig,
NewExportFileBackendSettingsFromConfig, ConfigToFileBackendSettings,
SetDefaults, the System Console (File Storage and Export Storage
panels) with matching i18n strings, and a dedicated Cypress spec.
Azurite test settings switch to AzureCloud=custom with the full
http://host:port/account/ URL so the existing integration suites
keep driving Azurite without any conditional URL plumbing inside the
driver. buildAzureServiceURL is now total: it returns an error for
custom-without-endpoint and for unknown enum values rather than
producing a malformed URL.

------
AI assisted commit

* Hide S3 File Storage fields when a different driver is selected

Match the existing Azure-field behavior: when the selected file storage
driver is not Amazon S3, hide the S3-only fields entirely instead of
just disabling them. Both driver groups already use this hide-when-
inactive pattern in the dedicated export storage section.

The Cypress assertion in azure_blob_storage_spec.js that codified the
old disable-only behavior is updated to expect not.exist.

------
AI assisted commit

* Address review feedback on AzureCloud validation and config-test endpoints

- Switch FileSettings.isValid AzureCloud / ExportAzureCloud value
  checks from slices.Contains to a switch statement, matching the
  closed-enum style used a few lines above for DriverName.
- Revert the io.EOF suppression in testFileStore and testEmail. The
  suppression was bot-suggested log-noise polish; reviewer prefers
  to keep the original error handling for those endpoints.
- Drop the redundant "http(s)" qualifier from the scheme check comment
  in buildAzureServiceURL.

------
AI assisted commit

---------

Co-authored-by: Mattermost Build <[email protected]>
2026-05-26 16:34:48 +00:00
e4b86e02e4 MM-68316: add mattermost db ping subcommand (#36406)
* 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).

* 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).

* MM-68316 Preserve safe db ping DSN errors

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-05-21 18:46:52 +00:00
6d3fc14911 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

---------

Co-authored-by: Ibrahim Serdar Acikgoz <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-05-15 21:04:32 +02:00
77dd5cc0af MM-68150: Upgrade golangci-lint to v2.12.2 (#36554)
* 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 <[email protected]>
2026-05-14 17:29:37 -04:00
Alejandro García MontoroandGitHub 0a981c295f 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 KrauserandGitHub 969b6a21f2 Migrate CPA to the v2 Property System (#36180) 2026-05-14 12:46:07 -04:00
Jesse HallamandGitHub edf2075381 MM-68149: Upgrade to Go 1.26.2 (#36418)
* MM-68149: upgrade to Go 1.26.2

Update go directive in go.mod and .go-version.

* MM-68149: replace pointer helpers with Go 1.26 new()

Go 1.26 extends the built-in new() to accept an initial value expression,
making typed-pointer helpers like model.NewPointer(x), bToP(x), and boolPtr(x)
redundant. Replace every call site with new(x) and remove the now-unused
helper functions and their //go:fix inline directives.

* MM-68149: apply go fix for reflect API and format-string changes

- reflect.Ptr → reflect.Pointer (renamed in Go 1.18, deprecated alias removed in 1.26)
- reflect range-over-struct: for i := 0; i < t.NumField(); i++ → for field := range t.Fields()
  and the equivalent for Methods() and interface types
- Fix format-string concatenation and variadic-arg mismatches flagged by go vet

* MM-68149: update JPEG fixtures and test infrastructure for Go 1.26 encoder

Go 1.26 ships a new image/jpeg encoder that produces slightly different output.
Regenerate all JPEG fixture files and switch the comparison helpers from
byte-equality to pixel-level comparison with a small per-channel tolerance,
so minor encoder drift across patch versions is handled automatically.

Add -update-fixtures flag to make it easy to regenerate fixtures after future
major Go upgrades. Document the update procedure in tests/README.md.

* MM-68149: CI check that go fix ./... produces no changes

* Fix real bugs flagged by CodeRabbit review

- group.go: set newGroup.MemberCount not group.MemberCount (member count
  was populated on the wrong variable and lost before publish/return)
- file_test.go: guard compareImage(GetFilePreview) on the preview slice
  length, not the thumbnail slice length (copy-paste error)
- config_test.go: remove duplicate MinimumLength assignment

* fixup! Fix real bugs flagged by CodeRabbit review
2026-05-12 15:59:12 +00:00
Jesse HallamandGitHub 6cbbb10a5e ci: compile mmctl e2e tests with requirefips when FIPS_ENABLED=true (#36267)
* ci: compile mmctl e2e tests with requirefips when FIPS_ENABLED=true

Without this, the mmctl test binary was compiled without the requirefips
tag even in the FIPS container, leaving model.FIPSEnabled=false and
PasswordSettings.MinimumLength=8. Short passwords like "somepass" passed
validation and hashing silently succeeded, giving false confidence that
the tests were FIPS-clean.

* tests: fix short password in TestUserConvertCmdF for FIPS

"Valid bot to user convert" reached ConvertBotToUser with "password"
(8 chars), which fails MinimumLength=14 on FIPS builds.
2026-04-28 00:41:57 +00:00
d4a058e754 [MM-68351] Fix nil pointer panic in mmctl websocket command on connection failure (#36138)
* fix(mmctl): prevent nil pointer panic in websocket command on connection failure

When the WebSocket connection fails immediately, Listen() closes EventChannel
via defer. Reading from a closed channel with a plain receive returns nil,
causing a panic in ToJSON(). Switch to range so the loop exits cleanly,
add a nil guard, and surface ListenError to the caller.

Fixes MM-68351

* test(mmctl): add unit tests for websocket nil event and ListenError handling

Extracts the event-processing loop into processWebSocketEvents to enable
unit testing, and adds tests covering the nil-event skip and error surfacing.

* test(mmctl): add happy-path subtest for processWebSocketEvents

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-23 15:32:34 +02:00
e03d609871 ci: enable fullyparallel mode for server tests (#35816)
* ci: enable fullyparallel mode for server tests

Replace os.Setenv, os.Chdir, and global state mutations with
parallel-safe alternatives (t.Setenv, t.Chdir, test hooks) across
37 files. Refactor GetLogRootPath and MM_INSTALL_TYPE to use
package-level test hooks instead of environment variables.

This enables gotestsum --fullparallel, allowing all test packages
to run with maximum parallelism within each shard.

* ci: split fullyparallel from continue-on-error in workflow template

- Add new boolean input 'allow-failure' separate from 'fullyparallel'
- Change continue-on-error to use allow-failure instead of fullyparallel
- Update server-ci.yml to pass allow-failure: true for test coverage job
- Allows independent control of parallel execution and failure tolerance

* fix: protect TestOverrideLogRootPath with sync.Mutex for parallel tests

- Replace global var TestOverrideLogRootPath with mutex-protected functions
- Add SetTestOverrideLogRootPath() and getTestOverrideLogRootPath() functions
- Update GetLogRootPath() to use thread-safe getter
- Update all test files to use SetTestOverrideLogRootPath() with t.Cleanup()
- Fixes race condition when running tests with t.Parallel()

* fix: configure audit settings before server setup in tests

- Move ExperimentalAuditSettings from UpdateConfig() to config defaults
- Pass audit config via app.Config() option in SetupWithServerOptions()
- Fixes audit test setup ordering to configure BEFORE server initialization
- Resolves CodeRabbit's audit config timing issue in api4 tests

* fix: implement SetTestOverrideLogRootPath mutex in logger.go

The previous commit updated test callers to use SetTestOverrideLogRootPath()
but didn't actually create the function in config/logger.go, causing build
failures across all CI shards. This commit:

- Replaces the exported var TestOverrideLogRootPath with mutex-protected
  unexported state (testOverrideLogRootPath + testOverrideLogRootMu)
- Adds exported SetTestOverrideLogRootPath() setter
- Adds unexported getTestOverrideLogRootPath() getter
- Updates GetLogRootPath() to use the thread-safe getter
- Fixes log_test.go callers that were missed in the previous commit

* fix(test): use SetupConfig for access_control feature flag registration

InitAccessControlPolicy() checks FeatureFlags.AttributeBasedAccessControl
at route registration time during server startup. Setting the flag via
UpdateConfig after Setup() is too late — routes are never registered
and API calls return 404.

Use SetupConfig() to pass the feature flag in the initial config before
server startup, ensuring routes are properly registered.

* fix(test): restore BurnOnRead flag state in TestRevealPost subtest

The 'feature not enabled' subtest disables BurnOnRead without restoring
it via t.Cleanup. Subsequent subtests inherit the disabled state, which
can cause 501 errors when they expect the feature to be available.

Add t.Cleanup to restore FeatureFlags.BurnOnRead = true after the
subtest completes.

* fix(test): restore EnableSharedChannelsMemberSync flag via t.Cleanup

The test disables EnableSharedChannelsMemberSync without restoring it.
If the subtest exits early (e.g., require failure), later sibling
subtests inherit a disabled flag and become flaky.

Add t.Cleanup to restore the flag after the subtest completes.

* Fix test parallelism: use instance-scoped overrides and init-time audit config

  Replace package-level test globals (TestOverrideInstallType,
  SetTestOverrideLogRootPath) with fields on PlatformService so each test
  gets its own instance without process-wide mutation. Fix three audit
  tests (TestUserLoginAudit, TestLogoutAuditAuthStatus,
  TestUpdatePasswordAudit) that configured the audit logger after server
  init — the audit logger only reads config at startup, so pass audit
  settings via app.Config() at init time instead.

  Also revert the Go 1.24.13 downgrade and bump mattermost-govet to
  v2.0.2 for Go 1.25.8 compatibility.

* Fix audit unit tests

* Fix MMCLOUDURL unit tests

* Fixed unit tests using MM_NOTIFY_ADMIN_COOL_OFF_DAYS

* Make app migrations idempotent for parallel test safety

  Change System().Save() to System().SaveOrUpdate() in all migration
  completion markers. When two parallel tests share a database pool entry,
  both may race through the check-then-insert migration pattern. Save()
  causes a duplicate key fatal crash; SaveOrUpdate() makes the second
  write a harmless no-op.

* test: address review feedback on fullyparallel PR

- Use SetLogRootPathOverride() setter instead of direct field access
  in platform/support_packet_test.go and platform/log_test.go (pvev)
- Restore TestGetLogRootPath in config/logger_test.go to keep
  MM_LOG_PATH env var coverage; test uses t.Setenv so it runs
  serially which is fine (pvev)
- Fix misleading comment in config_test.go: code uses t.Setenv,
  not os.Setenv (jgheithcock)

* fix: add missing os import in post_test.go

The os import was dropped during a merge conflict resolution while
burn-on-read shared channel tests from master still use os.Setenv.

---------

Co-authored-by: wiggin77 <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-08 20:48:36 -04:00
1bdd985b41 Generate instead of hard-coding test passwords, enforce new minimum for FIPS, shard CI, fix FIPS builds (#35905)
* Replace hardcoded test passwords with model.NewTestPassword()

Add model.NewTestPassword() utility that generates 14+ character
passwords meeting complexity requirements for FIPS compliance. Replace
all short hardcoded test passwords across the test suite with calls to
this function.

* Enforce FIPS compliance for passwords and HMAC keys

FIPS OpenSSL requires HMAC keys to be at least 14 bytes. PBKDF2 uses
the password as the HMAC key internally, so short passwords cause
PKCS5_PBKDF2_HMAC to fail.

- Add FIPSEnabled and PasswordFIPSMinimumLength build-tag constants
- Raise the password minimum length floor to 14 when compiled with
  requirefips, applied in SetDefaults only when unset and validated
  independently in IsValid
- Return ErrMismatchedHashAndPassword for too-short passwords in
  PBKDF2 CompareHashAndPassword rather than a cryptic OpenSSL error
- Validate atmos/camo HMAC key length under FIPS and lengthen test
  keys accordingly
- Adjust password validation tests to use PasswordFIPSMinimumLength
  so they work under both FIPS and non-FIPS builds

* CI: shard FIPS test suite and extract merge template

Run FIPS tests on PRs that touch go.mod or have 'fips' in the branch
name. Shard FIPS tests across 4 runners matching the normal Postgres
suite. Extract the test result merge logic into a reusable workflow
template to deduplicate the normal and FIPS merge jobs.

* more

* Fix email test helper to respect FIPS minimum password length

* Fix test helpers to respect FIPS minimum password length

* Remove unnecessary "disable strict password requirements" blocks from test helpers

* Fix CodeRabbit review comments on PR #35905

- Add server-test-merge-template.yml to server-ci.yml pull_request.paths
  so changes to the reusable merge workflow trigger Server CI validation
- Skip merge-postgres-fips-test-results job when test-postgres-normal-fips
  was skipped, preventing failures due to missing artifacts
- Set guest.Password on returned guest in CreateGuestAndClient helper
  to keep contract consistent with CreateUserWithClient
- Use shared LowercaseLetters/UppercaseLetters/NUMBERS/PasswordFIPSMinimumLength
  constants in NewTestPassword() to avoid drift if FIPS floor changes

* Rename FIPS test artifact to match server-ci-report pattern

The server-ci-report job searches for artifacts matching "*-test-logs",
so rename from postgres-server-test-logs-fips to
postgres-server-fips-test-logs to be included in the report.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-08 16:49:43 -03:00
Jesse HallamandGitHub fbb57f0e29 [MM-67859] Update license renewal and expiry email branding (#35701) 2026-04-04 20:30:36 -03:00
a7c122fc3b chore: fix typos in comments (#34960)
Co-authored-by: Mattermost Build <[email protected]>
2026-04-03 13:43:25 +00:00
de986ae735 Add property system app layer architecture (#35157)
* Refactor property system with app layer routing and access control separation

Establish the app layer as the primary entry point for property operations
with intelligent routing based on group type. This architecture separates
access-controlled operations (CPA groups) from standard operations,
improving performance and code clarity.

Architecture Changes:
- App layer now routes operations based on group type:
  - CPA groups -> PropertyAccessService (enforces access control)
  - Non-CPA groups -> PropertyService (direct, no access control)
- PropertyAccessService simplified to handle only CPA operations
- Eliminated redundant group type checks throughout the codebase

* Move access control routing into PropertyService

This change makes the PropertyService the main entrypoint for property
related operations, and adds a routing mechanism to decide if extra
behaviors or checks should run for each operation, in this case, the
property access service logic.

To add specific payloads that pluggable checks and operations may
need, we use the request context. When the request comes from the API,
the endpoints are in charge of adding the caller ID to the payload,
and in the case of the plugin API, on receiving a request, the server
automatically tags the context with the plugin ID so the property
service can react accordingly.

Finally, the new design enforces all these checks migrating the actual
property logic to internal, non-exposed methods, so any caller from
the App layer needs to go through the service checks that decide if
pluggable logic is needed, avoiding any possibility of a bypass.

* Fix i18n

* Fix bad error string

* Added nil guards to property methods

* Add check for multiple group IDs on value operations

* Add nil guard to the plugin checker

* Fix build error

* Update value tests

* Fix linter

* Adds early return when content flaggin a thread with no replies

* Fix mocks

* Clean the state of plugin property tests before each run

* Do not wrap appErr on API response and fix i18n

* Fix create property field test

* Remove the need to cache cpaGroupID as part of the property service

* Split the property.go file into multiple

* Not found group doesn't bypass access control check

* Unexport SetPluginCheckerForTests

* Rename plugin context getter to be more PSA specific

---------

Co-authored-by: Miguel de la Cruz <[email protected]>
2026-03-26 07:54:50 +00:00
8733a3e5ce ci: cache prepackaged plugins in mmctl tests (#35720)
* ci: cache prepackaged plugins in mmctl tests

Cache the prepackaged_plugins directory between mmctl test runs using
actions/cache. The cache key is derived from a SHA-256 hash of the
PLUGIN_PACKAGES lines in server/Makefile, so the cache automatically
invalidates whenever any plugin version is bumped — no manual key
updates needed.

Uses actions/cache output (cache-hit) to skip the download step
entirely on cache hits.

Saves ~1-2 minutes per mmctl test run by avoiding repeated plugin
downloads.

* fix: align plugin cache key with actual downloaded packages

Address CodeRabbit review: cache key was derived from all 15 plugins
in the Makefile, but the download only fetched jira plugin. Now both
the hash and the download use the same MMCTL_PLUGIN_PACKAGES value,
defined once, preventing stale cache or unnecessary invalidation.

* Revert "fix: align plugin cache key with actual downloaded packages"

This reverts commit 73303864bf1c066a49f649bcdab5c2201b38dd1f.

* Revert "ci: cache prepackaged plugins in mmctl tests"

This reverts commit acd1ec757afda3e6f5e4907c3dc92aa7765563f1.

* prepackage the existing test plugin to speed up tests

* fix: use public key for plugin signature verification in test

---------

Co-authored-by: Jesse Hallam <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-03-22 22:34:33 +08:00
71fcf94c5e [MM-67744] Add -buildvcs=false to default GOFLAGS (#35587)
* Add -buildvcs=false to default GOFLAGS

This prevents Go from embedding VCS information into binaries, which
avoids false positives in container vulnerability scanners like Trivy
when using Go workspaces with enterprise dependencies.

Also updates mmctl-build target to use $(GO) and $(GOFLAGS) for
consistency with other build targets.

Made-with: Cursor

* Update comment wording

Trigger PR sync to test Enterprise CI

Made-with: Cursor

* Trigger CI to test Enterprise CI fix

Made-with: Cursor

* Test Enterprise CI

Made-with: Cursor

* replace buildvcs metadata in mmctl

* rm redundant -buildvcs=false in GitHub actions

* update mmctl-docs to $(GO)

* simplify getVersionInfo signature

* use GOOS/GOARCH convention

* export GOFLAGS for common use

* Clarify version.go var block comment

---------

Co-authored-by: Jesse Hallam <[email protected]>
2026-03-20 10:28:36 -03:00
8b11064356 Fix import failures for Japanese filenames with dakuten on macOS (#35204)
* 🐛 fix: normalize Unicode filenames in import attachment lookup

Fix import failures for files with Japanese dakuten/handakuten characters
(e.g., ガ, パ, べ) on macOS.

macOS stores filenames in NFD (decomposed) form while Linux/Windows use
NFC (composed) form. This mismatch caused attachment lookup failures
when zip filenames and JSONL paths used different normalization forms.

Changes:
- Add NormalizeFilename utility function using golang.org/x/text/unicode/norm
- Normalize filenames when building attachment maps from zip files
- Normalize paths when looking up attachments in maps
- Apply fixes to both server (import.go) and mmctl (validate.go)

* avoid duplicating normalizeFilename

* add coverage for Korean filenames

---------

Co-authored-by: Jesse Hallam <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-03-18 12:16:55 +00:00
d03688add9 mmctl: Add support for listing user roles through mmctl (#34064)
Co-authored-by: Indusha Semba <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Ben Schumacher <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-03-18 11:43:13 +00:00
Domenico RizzoandGitHub 0f81a30c31 [MM-29973] Adds E2E tests for mmctl plugin disable (#35464) 2026-03-18 11:05:46 +00:00
Doug LauderandGitHub 489a62a555 Add --workers flag to mmctl import process to control concurrency (#35582)
* Add --workers flag to mmctl import process to control concurrency

   The bulk import worker count was hardcoded to runtime.NumCPU(), causing
   high database load on the master during imports on live systems. This is
   particularly impactful for incremental Slack imports where all users are
   re-imported each time, generating 8-15 DB operations per user against
   the master (due to LockToMaster).

   The new --workers flag allows administrators to reduce concurrency
   (e.g., --workers 1) to minimize impact on live users at the cost of
   longer import duration. Defaults to 0 which preserves the existing
   runtime.NumCPU() behavior.

* Add max workers limit, capped at CPU Count * 4
2026-03-13 21:30:32 -04:00
Felipe MartinandGitHub 29b13b946a fix: only match root-level JSONL files when importing a zip (#35481)
* fix: only match root-level JSONL files when importing a zip

When importing a Mattermost export zip, the code iterated over all files
to find the first .jsonl by extension. Exported attachments under data/
could themselves be .jsonl files, causing the import to pick an
attachment as the manifest instead of the actual root-level JSONL file.

Extract an IsRootJsonlFile helper in the imports package and use it in
the import process worker, mmctl validator, and bulk import test to
restrict the search to files with no directory component.

* test: add integration test for import with nested JSONL decoy file

Verify that BulkImportWithPath correctly picks the root-level JSONL
manifest and ignores a decoy .jsonl inside a subdirectory, covering
the fix from ad7f230f06.
2026-03-05 15:20:17 +01:00
cdb370c39b MM-29974 Adds e2e tests to the plugin list command (#34866)
* Adds e2e tests to the plugin list command

Adds end-to-end tests to the pluginListCmdF function.
These tests verify scenarios where appropriate permissions
are not available, plugins are disabled, and success cases.

* Updates plugin list command E2E tests

Removes the `pluginArg` from `pluginListCmdF` calls within end-to-end tests.
Ensures test cases accurately evaluate the `pluginListCmdF` behavior when no specific plugin name is provided as an argument, improving test coverage for default listing scenarios.

Relates to MM-29974

* Simplifies plugin test defer cleanup

Refactors defer statements in plugin E2E tests to directly call `s.th.App.UpdateConfig`.
Removes redundant anonymous function wrappers, making the test cleanup code cleaner and more concise.

Relates to MM-29974

---------

Co-authored-by: Mattermost Build <[email protected]>
2026-03-03 07:40:58 +00:00