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.
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.
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]>
Plugins are separate processes. They read "Mattermost-User-Id" off the
request as a literal string, compiled against the published server/public
module -- not against this tree. Renaming it here did not rename it there.
It also produced a header name with a space in it, which is not a valid
header name, so what shipped was a request the plugin could not read and
would not have been able to read under any spelling. Every plugin HTTP
route answered 401: the Agents API, its admin config, the webapp's whole
agents surface.
The same rename hit "Mattermost-Plugin-ID", the push proxy's
"X-Mattermost-Auth" and "X-Mattermost-ServerID", and the session
attribute device header. These are wire protocol, and the wire is shared
with software that is not ours to rename.
Brand strings the product actually shows -- the site name, the MFA
issuer, email product names -- are untouched. The outbound user agent
stays ours but loses its space: HanzoTeam-Bot/1.1 is a product token,
"Hanzo Team-Bot/1.1" is two.
Co-authored-by: Hanzo Dev <[email protected]>
Renames the product across user-facing text -- 1475 lines, i18n and the
defaultMessage strings that must match it -- while leaving the names that
are code rather than prose: upstream copyright, Go import paths, the
@mattermost npm scope, i18n keys, Go template fields, and legacy theme
identifiers like AuthorType.Mattermost and the 'Mattermost' key that maps
an old theme to a new one. The rule is a word boundary on identifier
characters; MattermostEntry is a symbol, "Mattermost" in a sentence is
the product.
The theme goes monochrome. Hanzo and Hanzo Dark are the two built-ins and
Hanzo is the default, with the CSS custom properties that carry brand
colour outside the theme object -- sidebar, buttons, links, titles --
moved to black. Status colours stay: online, away, do-not-disturb and
error encode state, not brand, and flattening them to grey would cost
meaning to buy consistency.
The sign-in header drops the TEAM EDITION badge. It advertised which
licence tier you were on, and there are no tiers here. Booting without a
licence is now logged at debug rather than warned about, because it is
this server's normal condition rather than a fault.
Co-authored-by: Hanzo Dev <[email protected]>
* MM: add v0.5 plugin access control policy model, registry, and decision outcomes
Co-authored-by: nick.misasi <[email protected]>
* MM: add plugin access control PDP/PAP app-layer methods with fail-closed semantics
Co-authored-by: nick.misasi <[email protected]>
* MM: expose plugin access control API surface (EvaluateAccessControl + PAP/CEL methods)
Co-authored-by: nick.misasi <[email protected]>
* MM: add store-layer round-trip tests for v0.5 plugin access control policies
Co-authored-by: nick.misasi <[email protected]>
* MM: extract plugin access control app code into plugin_access_control.go
Co-authored-by: nick.misasi <[email protected]>
* MM: add atomic type-guarded AccessControlPolicyStore.DeleteIfType
Co-authored-by: nick.misasi <[email protected]>
* MM: plugin PAP hardening — atomic typed delete, indistinguishable 404s, audit every attempt
Co-authored-by: nick.misasi <[email protected]>
* MM: close plugin Get-by-ID TOCTOU via GetPolicyOfType; stamp save audit operation at entry
Co-authored-by: nick.misasi <[email protected]>
* Fix gob RPC poisoning from native attribute select options
NativeUserAttributeFields stored bool-select options as
[]map[string]string inside PropertyField.Attrs (map[string]any). gob
requires concrete types inside interface values to be registered, and
[]map[string]string is not registered in client_rpc.go, so encoding the
GetAccessControlFieldsAutocomplete reply failed and net/rpc shut down
the shared plugin API connection, breaking every subsequent plugin API
call.
Build the options from gob-registered containers ([]any/map[string]any)
instead; JSON output is byte-identical. Add gob round-trip regression
tests covering every plugin access control API reply payload: the
autocomplete response including native attribute fields (fails against
the old code), policies with JSON-decoded Props, visual AST condition
values of every runtime shape, expression check errors, query users
responses, and evaluation decisions.
Co-authored-by: nick.misasi <[email protected]>
* Move plugin access control gob-safety tests into their own file
Pure move: plugin_access_control_test.go crossed 1000 lines; the gob-safety
helper and TestPluginAccessControlGobSafety now live in
plugin_access_control_gob_test.go, unchanged.
Co-authored-by: nick.misasi <[email protected]>
* MM: resolve plugin policy existence when ABAC is unavailable (Option B)
Every evaluation-impossible branch of EvaluatePluginAccessRequest
(service nil / unlicensed / flag off / user load or subject build
failure / evaluator infra error / unknown outcome) now performs a raw
open-core store read on the already-validated resource ID: no stored
row returns no_policy so the caller can safely apply legacy behavior;
any stored row (with a Warn on a foreign-type anomaly) or a failed
read returns unavailable so the caller must fail closed. This lets the
plugin drop its local policy index entirely.
Strengthens the EvaluateAccessControl doc contract accordingly and
reworks the fail-closed test matrix with with/without-row splits per
branch, a foreign-type-row case, a store-read-error case on the store
mock, and passthrough rows pinning that the fallback read never runs
when the evaluator answers.
Co-authored-by: nick.misasi <[email protected]>
* Clarify EvaluateAccessControl failure-mapping doc
A failure with a definitive store miss maps to no_policy under the
Option B semantics, so 'failures never map to allow or no_policy' was
inaccurate. State precisely: never allow; no_policy only on positively
determined non-existence; deny for defensive failures on a resolved
policy; unavailable otherwise.
Co-authored-by: nick.misasi <[email protected]>
* Tighten ABAC comments
Co-authored-by: nick.misasi <[email protected]>
* Document AccessDecision.Outcome as plugin-API-only
The evaluator populates Outcome on every lane, but the only production
reader is the app layer's EvaluatePluginAccessRequest, which maps it
into PluginAccessControlDecision; core channel/team enforcement reads
the collapsed Decision bool alone. State that on the field.
Co-authored-by: nick.misasi <[email protected]>
* Retrigger CI to rebuild enterprise image with updated enterprise branch
Co-authored-by: nick.misasi <[email protected]>
* Document that only the evaluator's plugin lane sets AccessDecision.Outcome
Co-authored-by: nick.misasi <[email protected]>
* ABAC: key plugin resource types as plugin_id:type and drop the static registry
Co-authored-by: nick.misasi <[email protected]>
* ABAC: replace AccessDecision.Outcome with the AuthZEN decision context reason
Co-authored-by: nick.misasi <[email protected]>
* ABAC: drop type-scoped policy get/delete and check the type in the app layer
Co-authored-by: nick.misasi <[email protected]>
* ABAC: compare plugin policy type ownership exactly instead of case-insensitively
Co-authored-by: nick.misasi <[email protected]>
* ABAC: bound the whole policy type to the Type column width
Co-authored-by: nick.misasi <[email protected]>
* ABAC: require an allow before treating a decision as the no-policy fallback
Co-authored-by: nick.misasi <[email protected]>
* ABAC: treat a colliding foreign-type policy row as no_policy, not a deny
Co-authored-by: nick.misasi <[email protected]>
* ABAC: gate plugin policy reads on a raw store read before normalization
Co-authored-by: nick.misasi <[email protected]>
* ABAC: pin policy type immutability on save in the store tests
Co-authored-by: nick.misasi <[email protected]>
* ABAC: reuse a single unavailable-error constructor in the existence fallback
Co-authored-by: nick.misasi <[email protected]>
* ABAC: re-check plugin policy ownership on the normalized get read
Co-authored-by: nick.misasi <[email protected]>
* ABAC: confirm plugin policy ownership before surfacing a normalization error
Co-authored-by: nick.misasi <[email protected]>
---------
Co-authored-by: Hanzo Dev <[email protected]>
* Fix plugin RPC teardown race in Shutdown and Deactivate
Environment.Shutdown() closed each plugin's RPC connection (via
supervisor.Shutdown) before removing it from registeredPlugins, leaving
a window where IsActive still returned true for a plugin whose transport
was already dead. Any concurrent RunMultiPluginHook* call in that window
would dispatch to the dead connection and log a spurious
"connection is shut down" error.
The fix: set PluginStateNotRunning immediately before supervisor.Shutdown
on every teardown path, so IsActive returns false before any dispatcher
can reach the closed transport. The state remains Running during
OnDeactivate so a plugin can still call back into the server (e.g.
CreatePost) and dispatch hooks to itself while its connection is live.
* fixup! Fix plugin RPC teardown race in Shutdown and Deactivate
* fixup! Fix plugin RPC teardown race in Shutdown and Deactivate
* Skip OnDeactivate teardown for already-inactive plugins
deactivateAndTeardown ran OnDeactivate and closed the RPC connection
guarding only on a nil supervisor. Shutdown calls it for every
registered plugin, so a plugin already deactivated (state NotRunning,
but still registered with a shut-down supervisor) had OnDeactivate
dispatched over its dead transport, logging a spurious 'connection is
shut down' error.
Fold the IsActive check into deactivateAndTeardown so both Shutdown and
Deactivate skip OnDeactivate and supervisor.Shutdown when the plugin
isn't running, only reconciling state to NotRunning.
* document and test intentional transition to PluginStatusNotRunning
* perf: skip ConsumePostHook clones when no plugin implements it
applyPostsWillBeConsumedHook deep-cloned every returned post via
Post.ForPlugin() on each batch read path before dispatching the
MessagesWillBeConsumed hook, even when no plugin implements it. Add
Environment.HasPluginImplementing(hookId) — the same cheap
supervisor.Implements scan RunMultiPluginHook does internally, minus the
clone, closure, and metrics observe — and guard both apply helpers on it
so stock servers pay nothing for the gate.
* [MM-69229] Promote ConsumePostHook: remove the feature flag
The ConsumePostHook flag (default false, shipped v9.3) gated the public
MessagesWillBeConsumed plugin hook. With the per-read clone now skipped
when no plugin implements the hook, the gate is safe to remove and the
hook becomes always-on (still subject to HasPluginImplementing and the
burn-on-read carve-out).
Drops the struct field, its SetDefaults entry, the e2e default_config
mirror, and the flag-toggling tests that no longer have a flag to toggle.
* Fix early return to also check MessagesWillBeConsumedWithContext
* allow workflow_dispatch trigger for Server CI (for plugins CI)
* [MM-68402] MBE Phase 2: declare four generic plugin hooks (#36291)
* new hooks-only phase 2
* remove ChannelWillBeMoved
* remove RecapWillBeProcessed and MessageWillBeRewrittenByAI
Drop the AI/recap hooks from the new-hook surface; AI-LLM paths
remain uncovered in tech preview and are documented as residuals.
* [MM-68403] MBE Phase 3: ChannelGuards primitive (storage + cache + plugin API) (#36365)
* phase 3
* phase 3: register ChannelGuard mock in test setup helper
NewChannels' startup-time call to reloadGuardCache invokes
s.ChannelGuard().GetAll(); without an expectation on the mock store,
every test that sets up the server with GetMockStoreForSetupFunctions
panics during init.
* phase 3: register ChannelGuard mock in retrylayer test
retrylayer.New walks every store getter to wrap it; without the mock
expectation on ChannelGuard, TestRetry panics during layer construction.
* use rctx properly in the store methods
* phase 3: match rctx arg in testlib ChannelGuard mock
GetAll now takes request.CTX, so the testify expectation must include
mock.Anything; otherwise the call panics under the mocked store.
* phase 3: set api.ctx in TestChannelGuardLowercaseNormalization
The test constructs PluginAPI directly without a ctx, which used to
work when App.RegisterChannelGuard built its own EmptyContext. Now
that the App methods take rctx from the caller, the nil ctx panics
inside RequestContextWithMaster.
* [MM-68404] MBE Phase 4: App-layer plugin hook wiring (#36407)
* phase 4
* Fix nil rctx in TestChannelGuardLowercaseNormalization
The PluginAPI struct literal was missing ctx: rctx after a refactor
moved the rctx declaration below the struct construction, leaving
api.ctx as nil. This caused a nil pointer dereference in reloadGuardCache
when RegisterChannelGuard called store.RequestContextWithMaster(nil).
* Remove ChannelWillBeMoved hook call from MoveChannel (phase 4)
The hook and its ID were removed from mbe-phase-2 but the call site in
MoveChannel and its i18n string were not cleaned up during the rebase.
* remove channel will be moved test
* Remove RecapWillBeProcessed and MessageWillBeRewrittenByAI hook calls (phase 4)
The hooks and their IDs were removed from mbe-phase-2 but the call sites
in ProcessRecapChannel and RewriteMessage, their i18n strings, and their
tests were not cleaned up during the rebase.
* Revert channel_id plumbing on rewrite endpoint (phase 4)
The channel_id field on RewriteRequest was added in phase 4 to feed the
synthetic post passed to MessageWillBeRewrittenByAI. With that hook
removed from mbe-phase-2, channel_id has no consumer; revert the field,
the api4 validation, the app.RewriteMessage parameter, and the
corresponding webapp client + hook plumbing.
* [MM-68555] MBE Phase 5: Channel-guard enforcement + two-phase dispatch (#36473)
* phase 5
* Bake plugin counter-file paths into source instead of env vars
t.Setenv panics when an ancestor test calls t.Parallel, so the two
channel-guard tests broke under ENABLE_FULLY_PARALLEL_TESTS in CI.
Build each plugin source per-subtest with its temp file path embedded
as a Go literal — same pattern as TestPluginUploadsAPI.
* Remove guarded helpers and tests for dropped hooks (phase 5)
The runGuardedRecapWillBeProcessed and runGuardedMessageWillBeRewrittenByAI
helpers were never wired (their app-layer call sites were already removed
in the phase-4 cleanup), and the corresponding sub-tests across panic /
allow / reject / partial plugins reference hooks that no longer exist.
* [MM-68405] MBE Phase 6: fire MessagesWillBeConsumed on the edit path (#36475)
---------
---------
---------
---------
* rebase onto master
---------
Co-authored-by: Hanzo Dev <[email protected]>
* MM-68838: ping restored plugin remote immediately on re-register
RegisterPluginForSharedChannels' restore branch updated the row but did
not call PingNow, leaving the restored remote offline until the next
pingLoop tick (up to PingFreq, default 1 minute). The new-connection
branch already calls PingNow; the restore branch now mirrors it so
sync attempts immediately after a plugin restart no longer fail with
"offline remote cluster".
* MM-68838: gob-encode error returns in apiRPCServer.ReceiveSharedChannelAttachmentSyncMsg
The apiRPCServer wrapper for ReceiveSharedChannelAttachmentSyncMsg
assigned the hook's error return directly to the gob-encoded response
struct. When the framework's App.ReceiveSharedChannelAttachmentSyncMsg
returned an error wrapped with %w (*fmt.wrapError, an unexported type),
gob refused to encode it and the RPC server broke the connection with
"type not registered for interface: fmt.wrapError".
Every subsequent plugin/server RPC call then returned the zero-value
response struct, causing plugins that dereferenced the nil returns to
crash.
Apply the existing encodableError() helper so the returned error
becomes a gob-safe ErrorString, matching every other apiRPCServer
method in this file.
* 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
* feat: include connection id in the plugin context
* refactor: group ConnectionId next to SessionId in plugin Context
Addresses review feedback to keep related identifier fields adjacent.
* fix(files): forward Connection-Id on file uploads to plugin hooks
The webapp uploadFile XHR didn't attach the Connection-Id header, so
FileWillBeUploaded plugin hooks always received an empty ConnectionId.
Read it from the websocket selector and set it on the request, matching
how drafts and channel bookmarks already do it. Adds a server-side test
asserting the connection id propagates through pluginContext.
* fix(lint): reorder file_actions imports to satisfy import/order
* Document ConnectionId on request.Context
* MM-68705 - Make ReceiveSharedChannelAttachmentSyncMsg order tolerant
Allow plugin remotes to invoke ReceiveSharedChannelAttachmentSyncMsg
and ReceiveSharedChannelSyncMsg in either order without losing the
post/file binding, and make repeat deliveries of the same file id
idempotent.
Two localised changes inside ReceiveSharedChannelAttachmentSyncMsg:
- Idempotency check before CreateUploadSession. If a FileInfo with
the sender's id already exists for the same channel and creator,
return it instead of inserting a duplicate (which would violate
the FileInfo PK and force the caller to retry indefinitely after
any transient ack failure). A mismatched channel or creator on the
same id is rejected.
- Lazy bind to post after UploadData. When the matching post is
already present (post-then-file ordering), CreatePost will have
stripped the unmatched file id from Post.FileIds; AttachToPost
plus Post.Overwrite restore the binding. When the post is not
yet present (file-then-post ordering), the FileInfo is left
unbound so the eventual post arrival's CreatePost path binds it.
The post is fetched first because AttachToPost is a blind UPDATE
that does not validate post existence, so calling it before the
post exists would orphan the FileInfo.
No other paths change. Cluster (non-plugin) shared-channel attachments,
ReceiveSharedChannelProfileImageSyncMsg, and UI uploads are unaffected.
New tests in shared_channel_test.go cover both orderings, repeated
receive success and rejection on channel/creator mismatch, and the
empty-PostId no-op.
* add WithRPCErr hooks (server-facing/internal only)
* zero _returns on RPC failure in WithRPCErr companions
Aligns the WithRPCErr template with the HooksRPCErr godoc contract: when
g.client.Call returns a transport error, gob may have partially decoded the
reply. Reassign _returns to a zero value before destructuring so callers always
receive zeroed outputs alongside a non-nil transport error.
* rename HooksRPCErr to HooksWithRPCErr for naming consistency
Every related symbol uses the WithRPCErr suffix (MessageHasBeenPostedWithRPCErr,
RunMultiPluginHookWithRPCErr, RunMultiHookWithRPCErr, etc.). Aligning the
interface name removes the only outlier and makes the convention uniform.
* rename rpcErrImpl to hooksWithRPCErrImpl
Mirrors the existing hooksImpl/Hooks naming pattern on hooksTimerLayer.
* add supervisor.HooksWithRPCErr() and drop runtime type assertion
The old path did rp.supervisor.Hooks().(HooksWithRPCErr) and handled the
"doesn't implement" branch — but that branch was structurally unreachable
(the compile-time `_ HooksWithRPCErr = (*hooksTimerLayer)(nil)` assertion
guards it).
Change supervisor.hooks from `Hooks` to the concrete `*hooksTimerLayer`
(which implements both interfaces, enforced at field assignment), add a
parallel HooksWithRPCErr() accessor, and call it directly. Hooks() keeps
its public Hooks-interface signature via implicit conversion at return.
* drop "implemented by" clause from HooksWithRPCErr godoc
Both hooksRPCClient and hooksTimerLayer satisfy the interface, and naming
implementations in interface godocs adds rot — the contract is what readers
need, not the list of wrappers.
---------
Co-authored-by: Hanzo Dev <[email protected]>
* Add XML struct tags and multi-remote registration for shared channels plugin API
Phase 1: Add xml struct tags to model types used in SyncMsg (Post, User,
Reaction, Status, PostAcknowledgement, FileInfo, SyncResponse,
MembershipChangeMsg). Add custom MarshalXML/UnmarshalXML for SyncMsg
(Users map, MentionTransforms map), StringMap, and StringInterface.
Exclude Post.Metadata, PrevStatus, and server-internal FileInfo fields
from XML. JSON serialization is unaffected.
Phase 2: Lift the one-remote-per-plugin constraint so plugins can
register multiple remotes with different SiteURLs. Add SiteURL field to
RegisterPluginOpts (defaults to "plugin_<PluginID>" for backward
compatibility). Add GetAllByPluginID and GetBySiteURL store methods.
Rewrite registration to dedup by SiteURL instead of PluginID. Add
UnregisterPluginRemoteForSharedChannels for single-remote removal with
plugin ownership validation. Validate SiteURL is non-empty in
RemoteCluster.IsValid. Simplify IsPlugin() to check PluginID only.
* 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]>
* updated go to version 1.25.8
* updated gotestsum version to work with go 1.25.8
go 1.25 does not work with indirect tools 0.11 dependency pulled by
gotestsum.
* Use sync.WaitGroup.Go to simplify goroutine creation
Replace the wg.Add(1) + go func() { defer wg.Done() }() pattern with
wg.Go(), which was introduced in Go 1.25.
* pushes fips image on workflow dispatch to allow fips test to run on go version update
* fix new requirements for FIPS compliance imposed on updating to go 1.25.8
* updates openssl symbol check for library shipped with FIPS new versions
go-openssl v2 shipped with FIPS versions starting from 1.25 uses mkcgo to generate
bindings causing symbol names to be different.
* removes temp workflow-dispatch condition
* keep versions out of agents md file
* MM-68016, MM-68017, MM-68018 Add plugin pre-hooks for channel member add, team member add, and channel archive
Enable plugins to intercept and reject (or modify) three operations before
they are persisted: adding a channel member, adding a team member, and
archiving a channel. These are the three high-priority hooks from epic
MM-68003.
* feat: filewillbedonwloaded hook
* feat: error popup
* chore: make generated pluginapi
* tests
* feat: different errors for different download types
* feat: allow toast positions
* fix: avoid using deprecated i18n function
* feat: add plugin API to show toasts
* feat: downloadType parameter
* tests: updated tests
* chore: make check-style
* chore: i18n
* chore: missing fields in tests
* chore: sorted i18n for webapp
* chore: run mmjstool
* test: fixed webapp tests with new changes
* test: missing mocks
* fix: ensure one-file attachments (previews) are handler properly as thumbnails
* chore: lint
* test: added new logic to tests
* chore: lint
* Add SendToastMessage API and FileWillBeDownloaded hook
- Introduced SendToastMessage method for sending toast notifications to users with customizable options.
- Added FileWillBeDownloaded hook to handle file download requests, allowing plugins to control access to files.
- Updated related types and constants for file download handling.
- Enhanced PluginSettings to include HookTimeoutSeconds for better timeout management.
* Update webapp/channels/src/components/single_image_view/single_image_view.tsx
* chore: copilot reviews
* test: head requests
* chore: linted the webapp
* tests: fixed path
* test: fixed mocked args
* allow sending message to a connection directly
* fix: hook thread safety
* chore: formatting
* chore: remove configuration from system console
* chore: release version
* chore: update signature
* chore: update release version
* chore: addressed comments
* fix: update file rejection handling to use 403 Forbidden status and include rejection reason header
* Fix nil pointer panic in runFileWillBeDownloadedHook
The atomic.Value in runFileWillBeDownloadedHook can be nil if no
plugins implement the FileWillBeDownloaded hook. This causes a panic
when trying to assert the nil interface to string.
This fix adds a nil check before the type assertion, defaulting to
an empty string (which allows the download) when no hooks have run.
Fixes:
- TestUploadDataMultipart/success panic
- TestUploadDataMultipart/resume_success panic
* test: move the logout test last
* chore: restored accidential deletion
* chore: lint
* chore: make generated
* refactor: move websocket events to new package
* chore: go vet
* chore: missing mock
* chore: revert incorrect fmt
* chore: import ordering
* chore: npm i18n-extract
* chore: update constants.tsx from master
* chore: make i18n-extract
* revert: conflict merge
* fix: add missing isFileRejected prop to SingleImageView tests
* fix: mock fetch in SingleImageView tests for async thumbnail check
The component now performs an async fetch to check thumbnail availability
before rendering. Tests need to mock fetch and use waitFor to handle
the async state updates.
* refactor: move hook logic to app layer
* chore: update version to 11.5
* Scope file download rejection toast to the requesting connection
Thread the Connection-Id header through RunFileWillBeDownloadedHook and
sendFileDownloadRejectedEvent so the WebSocket event is sent only to the
connection that initiated the download, instead of all connections for
the user.
---------
Co-authored-by: Hanzo Dev <[email protected]>
* Remove legacy quoteColumnName() utility
Since Mattermost only supports PostgreSQL, the quoteColumnName() helper
that was designed to handle database-specific column quoting is no longer
needed. The function was a no-op that simply returned the column name
unchanged.
Remove the function from utils.go and update status_store.go to use
the "Manual" column name directly.
* Remove legacy driver checks from store.go
Since Mattermost only supports PostgreSQL, remove conditional checks
for different database drivers:
- Simplify specialSearchChars() to always return PostgreSQL-compatible chars
- Remove driver check from computeBinaryParam()
- Remove driver check from computeDefaultTextSearchConfig()
- Simplify GetDbVersion() to use PostgreSQL syntax directly
- Remove switch statement from ensureMinimumDBVersion()
- Remove unused driver parameter from versionString()
* Remove MySQL alternatives for batch delete operations
Since Mattermost only supports PostgreSQL, remove the MySQL-specific
DELETE...LIMIT syntax and keep only the PostgreSQL array-based approach:
- reaction_store.go: Use PostgreSQL array syntax for PermanentDeleteBatch
- file_info_store.go: Use PostgreSQL array syntax for PermanentDeleteBatch
- preference_store.go: Use PostgreSQL tuple IN subquery for DeleteInvalidVisibleDmsGms
* Remove MySQL alternatives for UPDATE...FROM syntax
Since Mattermost only supports PostgreSQL, remove the MySQL-specific
UPDATE syntax that joins tables differently:
- thread_store.go: Use PostgreSQL UPDATE...FROM syntax in
MarkAllAsReadByChannels and MarkAllAsReadByTeam
- post_store.go: Use PostgreSQL UPDATE...FROM syntax in deleteThreadFiles
* Remove MySQL alternatives for JSON and subquery operations
Since Mattermost only supports PostgreSQL, remove the MySQL-specific
JSON and subquery syntax:
- thread_store.go: Use PostgreSQL JSONB operators for updating participants
- access_control_policy_store.go: Use PostgreSQL JSONB @> operator for
querying JSON imports
- session_store.go: Use PostgreSQL subquery syntax for Cleanup
- job_store.go: Use PostgreSQL subquery syntax for Cleanup
* Remove MySQL alternatives for CTE queries
Since Mattermost only supports PostgreSQL, simplify code that
uses CTEs (Common Table Expressions):
- channel_store.go: Remove MySQL CASE-based fallback in
UpdateLastViewedAt and use PostgreSQL CTE exclusively
- draft_store.go: Remove driver checks in DeleteEmptyDraftsByCreateAtAndUserId,
DeleteOrphanDraftsByCreateAtAndUserId, and determineMaxDraftSize
* Remove driver checks in migrate.go and schema_dump.go
Simplify migration code to use PostgreSQL driver directly since
PostgreSQL is the only supported database.
* Remove driver checks in sqlx_wrapper.go
Always apply lowercase named parameter transformation since PostgreSQL
is the only supported database.
* Remove driver checks in user_store.go
Simplify user store functions to use PostgreSQL-only code paths:
- Remove isPostgreSQL parameter from helper functions
- Use LEFT JOIN pattern instead of subqueries for bot filtering
- Always use case-insensitive LIKE with lower() for search
- Remove MySQL-specific role filtering alternatives
* Remove driver checks in post_store.go
Simplify post_store.go to use PostgreSQL-only code paths:
- Inline getParentsPostsPostgreSQL into getParentsPosts
- Use PostgreSQL TO_CHAR/TO_TIMESTAMP for date formatting in analytics
- Use PostgreSQL array syntax for batch deletes
- Simplify determineMaxPostSize to always use information_schema
- Use PostgreSQL jsonb subtraction for thread participants
- Always execute RefreshPostStats (PostgreSQL materialized views)
- Use materialized views for AnalyticsPostCountsByDay
- Simplify AnalyticsPostCountByTeam to always use countByTeam
* Remove driver checks in channel_store.go
Simplify channel_store.go to use PostgreSQL-only code paths:
- Always use sq.Dollar.ReplacePlaceholders for UNION queries
- Use PostgreSQL LEFT JOIN for retention policy exclusion
- Use PostgreSQL jsonb @> operator for access control policy imports
- Simplify buildLIKEClause to always use LOWER() for case-insensitive search
- Simplify buildFulltextClauseX to always use PostgreSQL to_tsvector/to_tsquery
- Simplify searchGroupChannelsQuery to use ARRAY_TO_STRING/ARRAY_AGG
* Remove driver checks in file_info_store.go
Simplify file_info_store.go to use PostgreSQL-only code paths:
- Always use PostgreSQL to_tsvector/to_tsquery for file search
- Use file_stats materialized view for CountAll()
- Use file_stats materialized view for GetStorageUsage() when not including deleted
- Always execute RefreshFileStats() for materialized view refresh
* Remove driver checks in attributes_store.go
Simplify attributes_store.go to use PostgreSQL-only code paths:
- Always execute RefreshAttributes() for materialized view refresh
- Remove isPostgreSQL parameter from generateSearchQueryForExpression
- Always use PostgreSQL LOWER() LIKE LOWER() syntax for case-insensitive search
* Remove driver checks in retention_policy_store.go
Simplify retention_policy_store.go to use PostgreSQL-only code paths:
- Remove isPostgres parameter from scanRetentionIdsForDeletion
- Always use pq.Array for scanning retention IDs
- Always use pq.Array for inserting retention IDs
- Remove unused json import
* Remove driver checks in property stores
Simplify property_field_store.go and property_value_store.go to use
PostgreSQL-only code paths:
- Always use PostgreSQL type casts (::text, ::jsonb, ::bigint, etc.)
- Remove isPostgres variable and conditionals
* Remove driver checks in channel_member_history_store.go
Simplify PermanentDeleteBatch to use PostgreSQL-only code path:
- Always use ctid-based subquery for DELETE with LIMIT
* Remove remaining driver checks in user_store.go
Simplify user_store.go to use PostgreSQL-only code paths:
- Use LEFT JOIN for bot exclusion in AnalyticsActiveCountForPeriod
- Use LEFT JOIN for bot exclusion in IsEmpty
* Simplify fulltext search by consolidating buildFulltextClause functions
Remove convertMySQLFullTextColumnsToPostgres and consolidate
buildFulltextClause and buildFulltextClauseX into a single function
that takes variadic column arguments and returns sq.Sqlizer.
* Simplify SQL stores leveraging PostgreSQL-only support
- Simplify UpdateMembersRole in channel_store.go and team_store.go
to use UPDATE...RETURNING instead of SELECT + UPDATE
- Simplify GetPostReminders in post_store.go to use DELETE...RETURNING
- Simplify DeleteOrphanedRows queries by removing MySQL workarounds
for subquery locking issues
- Simplify UpdateUserLastSyncAt to use UPDATE...FROM...RETURNING
instead of fetching user first then updating
- Remove MySQL index hint workarounds in ORDER BY clauses
- Update outdated comments referencing MySQL
- Consolidate buildFulltextClause and remove convertMySQLFullTextColumnsToPostgres
* Remove MySQL-specific test artifacts
- Delete unused MySQLStopWords variable and stop_word.go file
- Remove redundant testSearchEmailAddressesWithQuotes test
(already covered by testSearchEmailAddresses)
- Update comment that referenced MySQL query planning
* Remove MySQL references from server code outside sqlstore
- Update config example and DSN parsing docs to reflect PostgreSQL-only support
- Remove mysql:// scheme check from IsDatabaseDSN
- Simplify SanitizeDataSource to only handle PostgreSQL
- Remove outdated MySQL comments from model and plugin code
* Remove MySQL references from test files
- Update test DSNs to use PostgreSQL format
- Remove dead mysql-replica flag and replicaFlag variable
- Simplify tests that had MySQL/PostgreSQL branches
* Update docs and test config to use PostgreSQL
- Update mmctl config set example to use postgres driver
- Update test-config.json to use PostgreSQL DSN format
* Remove MySQL migration scripts, test data, and docker image
Delete MySQL-related files that are no longer needed:
- ESR upgrade scripts (esr.*.mysql.*.sql)
- MySQL schema dumps (mattermost-mysql-*.sql)
- MySQL replication test scripts (replica-*.sh, mysql-migration-test.sh)
- MySQL test warmup data (mysql_migration_warmup.sql)
- MySQL docker image reference from mirror-docker-images.json
* Remove MySQL references from webapp
- Simplify minimumHashtagLength description to remove MySQL-specific configuration note
- Remove unused HIDE_MYSQL_STATS_NOTIFICATION preference constant
- Update en.json i18n source file
* clean up e2e-tests
* rm server/tests/template.load
* Use teamMemberSliceColumns() in UpdateMembersRole RETURNING clause
Refactor to use the existing helper function instead of hardcoding
the column names, ensuring consistency if the columns are updated.
* u.id -> u.Id
* address code review feedback
---------
Co-authored-by: Mattermost Build <[email protected]>
* Add documentation for plugin RPC architecture
Document the bidirectional RPC communication between Mattermost server
and plugin processes. Added an architectural overview with ASCII diagram
and godoc comments for hooksRPCClient, hooksRPCServer, apiRPCClient,
and apiRPCServer explaining their roles in the plugin system.
* update
---------
Co-authored-by: Hanzo Dev <[email protected]>
* Fix regression in PluginHTTPStream where request body closed prematurely
When WriteHeader was called before reading the request body in inter-plugin
communication, the body would be closed prematurely due to defer r.Body.Close()
executing when the function returned (after starting the response goroutine).
This fix moves defer r.Body.Close() into the goroutine to ensure the request
body remains available until after the response is fully processed.
Added test case TestInterpluginPluginHTTPWithBodyAfterWriteHeader to verify
the fix and prevent future regressions.
* Fix resource leak by closing request body in all PluginHTTPStream error paths
---------
Co-authored-by: Christopher Speller <[email protected]>
* Implement property field limit enforcement and counting functionality in Plugin API
- Added a limit of 20 property fields per group in the CreatePropertyField method.
- Introduced CountPropertyFields method to count active and all property fields, including deleted ones.
- Enhanced tests to validate the new property field limit and counting behavior.
- Updated related API and service methods to support the new functionality.
* Update server/channels/app/properties/property_field.go
* fix vet
* fix lint error
* fix test
* fix tests
* fix test
* count properties + targets
* Update server/channels/app/plugin_api.go
* remove test for limit
* fix more tests
* improve testing messages now that the limit is removed
* Apply suggestion from @calebroseland
Co-authored-by: Caleb Roseland <[email protected]>
* Apply suggestion from @calebroseland
Co-authored-by: Caleb Roseland <[email protected]>
* Apply suggestion from @calebroseland
Co-authored-by: Caleb Roseland <[email protected]>
* Apply suggestion from @calebroseland
Co-authored-by: Caleb Roseland <[email protected]>
---------
Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Julien Tant <[email protected]>
Co-authored-by: Caleb Roseland <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
This commit exposes audit logging functionality to plugins via the plugin API, allowing plugins to create and log audit records. Additionally, it addresses a gob encoding issue that could cause plugin crashes when audit data contains nil pointers or unregistered types.
* Always require signatures for prepackaged plugins
We have always required signatures for packages installed via the marketplace -- whether remotely satisfied, or sourced from the prepackaged plugin cache.
However, prepackaged plugins discovered and automatically installed on
startup did not require a valid signature. Since we already ship
signatures for all Mattermost-authored prepackaged plugins, it's easy to
simply start requiring this.
Distributions of Mattermost that bundle their own prepackaged plugins
will have to include their own signatures. This in turn requires
distributing and configuring Mattermost with a custom public key via
`PluginSettings.SignaturePublicKeyFiles`.
Note that this enhanced security is neutered with a deployment that uses
a file-based `config.json`, as any exploit that allows appending to the
prepackaged plugins cache probably also allows modifying `config.json`
to register a new public key. A [database-based
config](https://docs.mattermost.com/configure/configuration-in-your-database.html)
is recommended.
Finally, we already support an optional setting
`PluginSettings.RequirePluginSignature` to always require a plugin
signature, although this effectively disables plugin uploads and
requires extra effort to deploy the corresponding signature. In
environments where only prepackaged plugins are used, this setting is
ideal.
Fixes: https://mattermost.atlassian.net/browse/MM-64627
* setup dev key, expect no plugins if sig fails
* Fix shadow variable errors in test helpers
Pre-declare signaturePublicKey variable in loops to avoid shadowing
the outer err variable used in error handling.
* Replace PrepackagedPlugin.Signature with SignaturePath for memory efficiency
- Changed PrepackagedPlugin struct to use SignaturePath string instead of Signature []byte
- Updated buildPrepackagedPlugin to use file descriptor instead of reading signature into memory
- Modified plugin installation and persistence to read from signature file paths
- Updated all tests to check SignaturePath instead of Signature field
- Removed unused bytes import from plugin.go
This change reduces memory usage by storing file paths instead of signature data
in memory while maintaining the same security verification functionality.
---------
Co-authored-by: Hanzo Dev <[email protected]>
* Upgrade Go to 1.24.3
Updates the following files:
- server/.go-version: 1.23.9 → 1.24.3
- server/build/Dockerfile.buildenv: golang:1.23.9-bullseye → golang:1.24.3-bullseye
- server/go.mod: go 1.23.0 → go 1.24.3, toolchain go1.23.9 → go1.24.3
- server/public/go.mod: go 1.23.0 → go 1.24.3, toolchain go1.23.9 → go1.24.3
Also fixes non-constant format string errors introduced by Go 1.24.3's stricter format string checking:
- Added response() helper function in slashcommands/util.go for simple string responses
- Removed unused responsef() function from slashcommands/util.go
- Replaced responsef() with response() for translated strings that don't need formatting
- Fixed fmt.Errorf and fmt.Fprintf calls to use proper format verbs instead of string concatenation
- Updated marketplace buildURL to handle format strings conditionally
* Update generated mocks for Go 1.24.3
Regenerated mocks using mockery v2.53.4 to ensure compatibility with Go 1.24.3.
This addresses mock generation failures that occurred with the Go upgrade.
* Update to bookworm and fix non-existent sha
Signed-off-by: Stavros Foteinopoulos <[email protected]>
* fix non-constant format string
---------
Signed-off-by: Stavros Foteinopoulos <[email protected]>
Co-authored-by: Stavros Foteinopoulos <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
There are several steps that a server runs through inside (*Server).Start
after ch.initPlugins() till the signal handler is reached which handles
the server shutdown procedure.
The issue arises when the server is shutdown after ch.initPlugins() completes
but before (*Server).Start finishes. In that case, the plugins are all started
but they won't be shut down cleanly.
To fix this edge-case, we set up an intermediate signal handler, which
attaches itself as soon as ch.initPlugins is finished, allowing us to run
the cleanup code in case the shutdown happens before (*Server).Start finishes.
And when we do reach the main signal handler, we don't need this intermediate
handler any more. So we just reset the handlers and use the main signal handler
which takes care of shutting down the whole server.
Note: This is still not 100% bug-proof because ch.initPlugins() will initialize
_all_ plugins, and the shutdown can happen just after one plugin is initialized.
To handle that case will require the need to set up signal handlers after every
plugin init which feels like overkill to me.
A sample flow diagram to visualize better:
Edge-case
server.Start()
|
ch.initPlugins()
|
<ctrl-c>
|
execute signal handler, os.Exit(1)
Happy-path
server.Start()
|
ch.initPlugins()
|
server.Start() finished
|
reset old signal handler
|
setup main signal handler
|
server runs on as usual until shutdown
https://mattermost.atlassian.net/browse/MM-49353
```release-note
NONE
```