snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
233
server/channels/app/properties/value_audit_test.go
Normal file
233
server/channels/app/properties/value_audit_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type recordingSink struct {
|
||||
calls []struct {
|
||||
rctx request.CTX
|
||||
event ValueAuditEvent
|
||||
}
|
||||
}
|
||||
|
||||
func (r *recordingSink) sink() ValueAuditSink {
|
||||
return func(_ request.CTX, e ValueAuditEvent) {
|
||||
r.calls = append(r.calls, struct {
|
||||
rctx request.CTX
|
||||
event ValueAuditEvent
|
||||
}{event: e})
|
||||
}
|
||||
}
|
||||
|
||||
func newRegisteredAuditHook(groupID string, sink ValueAuditSink) *PropertyValueAuditHook {
|
||||
h := NewPropertyValueAuditHook()
|
||||
h.RegisterGroup(groupID, sink)
|
||||
return h
|
||||
}
|
||||
|
||||
func registerCPAGroup(tb testing.TB, th *TestHelper) string {
|
||||
group, err := th.service.RegisterPropertyGroup(&model.PropertyGroup{Name: model.AccessControlPropertyGroupName, Version: model.PropertyGroupVersionV2})
|
||||
require.NoError(tb, err)
|
||||
return group.ID
|
||||
}
|
||||
|
||||
func TestPropertyValueAuditHook_PostCreate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
|
||||
newValue := func() *model.PropertyValue {
|
||||
return &model.PropertyValue{GroupID: managed, TargetType: "user", TargetID: "u1", FieldID: "f1", Value: []byte(`"v"`)}
|
||||
}
|
||||
|
||||
t.Run("audits a successful create unconditionally", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
v := newValue()
|
||||
require.NoError(t, hook.PostCreatePropertyValue(th.Context, v))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionCreate, rec.calls[0].event.Action)
|
||||
assert.Nil(t, rec.calls[0].event.Prev)
|
||||
assert.Equal(t, v, rec.calls[0].event.Current)
|
||||
})
|
||||
|
||||
t.Run("audits each value in a batch and ignores unregistered groups", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
other := newValue()
|
||||
other.GroupID = "other"
|
||||
require.NoError(t, hook.PostCreatePropertyValues(th.Context, []*model.PropertyValue{newValue(), other, newValue()}))
|
||||
require.Len(t, rec.calls, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPropertyValueAuditHook_PostUpdate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
|
||||
newValue := func() *model.PropertyValue {
|
||||
return &model.PropertyValue{GroupID: managed, TargetType: "user", TargetID: "u1", FieldID: "f1", Value: []byte(`"v"`)}
|
||||
}
|
||||
|
||||
t.Run("audits a changed value", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
next := newValue()
|
||||
next.Value = []byte(`"changed"`)
|
||||
require.NoError(t, hook.PostUpdatePropertyValue(th.Context, next))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionUpdate, rec.calls[0].event.Action)
|
||||
})
|
||||
|
||||
t.Run("audits an unchanged value", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
require.NoError(t, hook.PostUpdatePropertyValue(th.Context, newValue()))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionUpdate, rec.calls[0].event.Action)
|
||||
})
|
||||
|
||||
t.Run("audits each value in a batch", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
changed := newValue()
|
||||
changed.Value = []byte(`"changed"`)
|
||||
values := []*model.PropertyValue{newValue(), changed}
|
||||
require.NoError(t, hook.PostUpdatePropertyValues(th.Context, values))
|
||||
require.Len(t, rec.calls, 2)
|
||||
assert.Equal(t, ValueAuditActionUpdate, rec.calls[0].event.Action)
|
||||
assert.Equal(t, ValueAuditActionUpdate, rec.calls[1].event.Action)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPropertyValueAuditHook_PostUpsert(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
|
||||
newValue := func() *model.PropertyValue {
|
||||
return &model.PropertyValue{GroupID: managed, TargetType: "user", TargetID: "u1", FieldID: "f1", Value: []byte(`"v"`)}
|
||||
}
|
||||
|
||||
t.Run("audits a new value", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
require.NoError(t, hook.PostUpsertPropertyValue(th.Context, newValue()))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionUpsert, rec.calls[0].event.Action)
|
||||
assert.Equal(t, "f1", rec.calls[0].event.FieldID)
|
||||
})
|
||||
|
||||
t.Run("audits an unchanged value", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
require.NoError(t, hook.PostUpsertPropertyValue(th.Context, newValue()))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionUpsert, rec.calls[0].event.Action)
|
||||
})
|
||||
|
||||
t.Run("audits a changed value", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
next := newValue()
|
||||
next.Value = []byte(`"changed"`)
|
||||
require.NoError(t, hook.PostUpsertPropertyValue(th.Context, next))
|
||||
require.Len(t, rec.calls, 1)
|
||||
})
|
||||
|
||||
t.Run("ignores values in an unregistered group", func(t *testing.T) {
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
v := newValue()
|
||||
v.GroupID = "other"
|
||||
require.NoError(t, hook.PostUpsertPropertyValue(th.Context, v))
|
||||
assert.Empty(t, rec.calls)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPropertyValueAuditHook_PostDelete(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
|
||||
require.NoError(t, hook.PostDeletePropertyValue(th.Context, managed, "v1", &model.PropertyValue{GroupID: managed, TargetType: "user", TargetID: "u1", FieldID: "f1"}))
|
||||
require.NoError(t, hook.PostDeletePropertyValuesForTarget(th.Context, managed, "user", "u1"))
|
||||
require.NoError(t, hook.PostDeletePropertyValuesForField(th.Context, managed, "f1"))
|
||||
|
||||
// Unregistered group is ignored on every delete variant.
|
||||
require.NoError(t, hook.PostDeletePropertyValue(th.Context, "other", "v3", &model.PropertyValue{GroupID: "other"}))
|
||||
require.NoError(t, hook.PostDeletePropertyValuesForTarget(th.Context, "other", "user", "u1"))
|
||||
require.NoError(t, hook.PostDeletePropertyValuesForField(th.Context, "other", "f1"))
|
||||
|
||||
require.Len(t, rec.calls, 3)
|
||||
assert.Equal(t, ValueAuditActionDelete, rec.calls[0].event.Action)
|
||||
assert.Equal(t, "v1", rec.calls[0].event.ValueID)
|
||||
assert.Equal(t, ValueAuditActionDeleteForTarget, rec.calls[1].event.Action)
|
||||
assert.Equal(t, ValueAuditActionDeleteForField, rec.calls[2].event.Action)
|
||||
}
|
||||
|
||||
func TestPropertyValueAuditHook_PostDeleteSparseSnapshot(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
rec := &recordingSink{}
|
||||
hook := newRegisteredAuditHook(managed, rec.sink())
|
||||
|
||||
t.Run("audits a successful delete without a snapshot", func(t *testing.T) {
|
||||
require.NoError(t, hook.PostDeletePropertyValue(th.Context, managed, "value-id", nil))
|
||||
require.Len(t, rec.calls, 1)
|
||||
assert.Equal(t, ValueAuditActionDelete, rec.calls[0].event.Action)
|
||||
assert.Equal(t, "value-id", rec.calls[0].event.ValueID)
|
||||
})
|
||||
}
|
||||
|
||||
// TestPropertyValueAuditHook_ServiceUpsert exercises the full service path:
|
||||
// every write, including an identical re-upsert, is audited.
|
||||
func TestPropertyValueAuditHook_ServiceUpsert(t *testing.T) {
|
||||
th := Setup(t)
|
||||
managed := registerCPAGroup(t, th)
|
||||
rec := &recordingSink{}
|
||||
valueAuditHook := NewPropertyValueAuditHook()
|
||||
valueAuditHook.RegisterGroup(managed, rec.sink())
|
||||
th.service.AddHook(valueAuditHook)
|
||||
|
||||
field := th.CreatePropertyFieldDirect(t, &model.PropertyField{
|
||||
GroupID: managed,
|
||||
Name: "attr_" + model.NewId()[:8],
|
||||
Type: model.PropertyFieldTypeText,
|
||||
ObjectType: model.PropertyFieldObjectTypeUser,
|
||||
TargetType: string(model.PropertyFieldTargetLevelSystem),
|
||||
})
|
||||
|
||||
rctx := RequestContextWithCallerID(th.Context, model.CallerIDLDAPSync)
|
||||
value := &model.PropertyValue{
|
||||
GroupID: managed,
|
||||
FieldID: field.ID,
|
||||
TargetType: model.PropertyFieldObjectTypeUser,
|
||||
TargetID: model.NewId(),
|
||||
Value: []byte(`"synced"`),
|
||||
}
|
||||
|
||||
_, err := th.service.UpsertPropertyValue(rctx, value)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rec.calls, 1, "first write should audit")
|
||||
|
||||
rec.calls = nil
|
||||
same := &model.PropertyValue{
|
||||
GroupID: managed,
|
||||
FieldID: field.ID,
|
||||
TargetType: model.PropertyFieldObjectTypeUser,
|
||||
TargetID: value.TargetID,
|
||||
Value: []byte(`"synced"`),
|
||||
}
|
||||
_, err = th.service.UpsertPropertyValue(rctx, same)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rec.calls, 1, "identical re-write should audit")
|
||||
}
|
||||
Reference in New Issue
Block a user