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:
200
server/public/model/property_access.go
Normal file
200
server/public/model/property_access.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
// Property Field Access Control Attributes
|
||||
PropertyAttrsProtected = "protected"
|
||||
PropertyAttrsSourcePluginID = "source_plugin_id"
|
||||
PropertyAttrsAccessMode = "access_mode"
|
||||
PropertyAttrsOwners = "owners"
|
||||
|
||||
// Access Modes
|
||||
PropertyAccessModePublic = "" // Empty string means public (default)
|
||||
PropertyAccessModeSourceOnly = "source_only"
|
||||
PropertyAccessModeSharedOnly = "shared_only"
|
||||
)
|
||||
|
||||
// Property owner types. An owner is an identity trusted to manage an
|
||||
// attribute's data; see the Property Permissions Proposal.
|
||||
const (
|
||||
PropertyOwnerTypePlugin = "plugin"
|
||||
PropertyOwnerTypeService = "service"
|
||||
PropertyOwnerTypeRole = "role"
|
||||
PropertyOwnerTypeUser = "user"
|
||||
)
|
||||
|
||||
// Defensive bounds on the owners list. These are not semantic limits — the
|
||||
// server never interprets owner IDs or scopes — they only keep a buggy or
|
||||
// hostile owner from bloating the field's Attrs blob. Real usage is far below
|
||||
// them (a field has one or two owners, each with a handful of scopes). Scope
|
||||
// labels must match the identifier charset enforced by IsValidPropertyOwnerScope.
|
||||
const (
|
||||
PropertyOwnersMaxPerField = 20 // owners per field
|
||||
PropertyOwnerScopesMax = 32 // scopes per owner
|
||||
PropertyOwnerScopeMaxRunes = 64 // length of a single scope label
|
||||
PropertyOwnerIDMaxRunes = 255 // length of an owner id
|
||||
)
|
||||
|
||||
// PropertyOwner is an identity trusted to manage an attribute's data. When a
|
||||
// field carries one or more owners (in its Attrs blob under PropertyAttrsOwners)
|
||||
// the owners list governs the write-access decision for that field, superseding
|
||||
// the legacy protected / source_plugin_id gating and the sync-lock:
|
||||
// - A machine caller must be a listed owner to write values or edit/delete the
|
||||
// field. A listed owner may edit the whole field definition including the
|
||||
// owners list (no own-entry restriction). A non-owner machine caller cannot
|
||||
// modify the field or add itself; the first owner is bootstrapped by a
|
||||
// sysadmin via the REST API.
|
||||
// - Value writes by a machine caller are allowed only if it is a listed owner
|
||||
// (matching ID and Type) whose Scopes contain the caller's acting-as scope.
|
||||
// An owner with an empty Scopes list is not scope-restricted and may write
|
||||
// for any scope.
|
||||
// - Value writes by a human (session) caller are always rejected: an
|
||||
// owner-managed field's values are authoritative to the owning
|
||||
// integration, so no session user — including sysadmins — may overwrite
|
||||
// them. This is enforced in the property-service hook, mirroring the
|
||||
// ldap/saml sync lock; PermissionValues is left at the normal default.
|
||||
//
|
||||
// What it does NOT change: source_plugin_id immutability and the protected-flag
|
||||
// rules still apply as invariants on field-definition writes (a plugin-created
|
||||
// owner field still carries its source_plugin_id), and read/masking is unchanged
|
||||
// and still keys off access_mode.
|
||||
type PropertyOwner struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
// IsValidPropertyOwnerType reports whether the given owner type is recognized.
|
||||
func IsValidPropertyOwnerType(ownerType string) bool {
|
||||
switch ownerType {
|
||||
case PropertyOwnerTypePlugin,
|
||||
PropertyOwnerTypeService,
|
||||
PropertyOwnerTypeRole,
|
||||
PropertyOwnerTypeUser:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var validPropertyOwnerScopeChars = regexp.MustCompile(`^[a-zA-Z0-9._:-]+$`)
|
||||
|
||||
// IsValidPropertyOwnerScope reports whether a scope label is identifier-shaped.
|
||||
// This is a structural bound (like the length/count caps), not a semantic
|
||||
// registry: the server still does not interpret what a scope means.
|
||||
func IsValidPropertyOwnerScope(scope string) bool {
|
||||
return validPropertyOwnerScopeChars.MatchString(scope)
|
||||
}
|
||||
|
||||
// GetPropertyFieldOwners returns the owners declared on a field's Attrs blob.
|
||||
// Returns nil when the field has no owners. Handles both the typed
|
||||
// ([]PropertyOwner) shape used right after a write and the generic
|
||||
// ([]interface{} of maps) shape produced by a JSON round-trip from the store.
|
||||
func GetPropertyFieldOwners(field *PropertyField) []PropertyOwner {
|
||||
if field == nil || field.Attrs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, ok := field.Attrs[PropertyAttrsOwners]
|
||||
if !ok || raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if owners, ok := raw.([]PropertyOwner); ok {
|
||||
return owners
|
||||
}
|
||||
|
||||
data, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var owners []PropertyOwner
|
||||
if err := json.Unmarshal(data, &owners); err != nil {
|
||||
return nil
|
||||
}
|
||||
return owners
|
||||
}
|
||||
|
||||
// HasPropertyFieldOwners reports whether a field declares any owners.
|
||||
func HasPropertyFieldOwners(field *PropertyField) bool {
|
||||
return len(GetPropertyFieldOwners(field)) > 0
|
||||
}
|
||||
|
||||
// IsKnownPropertyAccessMode checks if the given access mode is a recognized value
|
||||
func IsKnownPropertyAccessMode(accessMode string) bool {
|
||||
switch accessMode {
|
||||
case PropertyAccessModePublic,
|
||||
PropertyAccessModeSourceOnly,
|
||||
PropertyAccessModeSharedOnly:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsPropertyFieldProtected returns whether a PropertyField is protected from modifications
|
||||
// by callers other than the source plugin
|
||||
func IsPropertyFieldProtected(field *PropertyField) bool {
|
||||
if field.Attrs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
protected, ok := field.Attrs[PropertyAttrsProtected].(bool)
|
||||
return ok && protected
|
||||
}
|
||||
|
||||
// GetAccessMode returns the field's access mode. Returns the public mode (empty
|
||||
// string) when no access_mode is configured or the field has no attrs at all.
|
||||
func (f *PropertyField) GetAccessMode() string {
|
||||
if f.Attrs == nil {
|
||||
return PropertyAccessModePublic
|
||||
}
|
||||
accessMode, ok := f.Attrs[PropertyAttrsAccessMode].(string)
|
||||
if !ok {
|
||||
return PropertyAccessModePublic
|
||||
}
|
||||
return accessMode
|
||||
}
|
||||
|
||||
// ValidatePropertyFieldAccessMode validates that the access_mode attribute is valid
|
||||
// and compatible with the field type
|
||||
func ValidatePropertyFieldAccessMode(field *PropertyField) error {
|
||||
if field.Attrs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
accessMode, ok := field.Attrs[PropertyAttrsAccessMode].(string)
|
||||
if !ok {
|
||||
// No access mode set, that's fine (defaults to public)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if access mode is known
|
||||
if !IsKnownPropertyAccessMode(accessMode) {
|
||||
return fmt.Errorf("invalid access mode '%s'", accessMode)
|
||||
}
|
||||
|
||||
// Validate that non-public access modes require protected flag
|
||||
if accessMode == PropertyAccessModeSourceOnly || accessMode == PropertyAccessModeSharedOnly {
|
||||
if !IsPropertyFieldProtected(field) {
|
||||
return fmt.Errorf("access mode '%s' requires the field to be protected", accessMode)
|
||||
}
|
||||
}
|
||||
|
||||
// shared_only + member-writable is contradictory: shared_only filters what
|
||||
// callers see to values they hold, but member-writable lets users self-assign
|
||||
// any value. Reject the combination at validation time instead of working
|
||||
// around it at the API/service layer.
|
||||
if accessMode == PropertyAccessModeSharedOnly && field.PermissionValues != nil && *field.PermissionValues == PermissionLevelMember {
|
||||
return fmt.Errorf("access mode 'shared_only' is incompatible with member-writable permission_values")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user