Files
upstream-mattermost/server/public/model/session_attributes.go
upstream-mirror 240db4e2a7
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
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

358 lines
16 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"maps"
"slices"
)
const SessionAttributesPropertyGroupName = "session_attributes"
const (
SessionAttributePlatformDesktop = "desktop"
SessionAttributePlatformMobile = "mobile"
SessionAttributePlatformBrowser = "browser"
)
const (
SessionAttributesPropertyFieldClientIPAddress = "client_ip_address"
SessionAttributesPropertyFieldNetworkInterfaceType = "network_interface_type"
SessionAttributesPropertyFieldVPNActive = "vpn_active"
SessionAttributesPropertyFieldSSID = "ssid"
SessionAttributesPropertyFieldTLSDDeviceID = "tls_device_id"
SessionAttributesPropertyFieldClientDeviceID = "client_device_id"
SessionAttributesPropertyFieldMDMEnrolled = "mdm_enrolled"
SessionAttributesPropertyFieldHardwareID = "hardware_id"
SessionAttributesPropertyFieldOSPlatform = "os_platform"
SessionAttributesPropertyFieldOSVersion = "os_version"
SessionAttributesPropertyFieldClientVersion = "client_version"
SessionAttributesPropertyFieldJailbreakDetected = "jailbreak_detected"
SessionAttributesPropertyFieldServerFQDN = "server_fqdn"
SessionAttributesPropertyFieldClientFQDN = "client_fqdn"
SessionAttributesPropertyFieldUserAgentPlatform = "user_agent_platform"
SessionAttributesPropertyFieldUserAgentOS = "user_agent_os"
SessionAttributesPropertyFieldUserAgentBrowserName = "user_agent_browser_name"
SessionAttributesPropertyFieldUserAgentBrowserVersion = "user_agent_browser_version"
SessionAttributesPropertyFieldIPAddress = "ip_address"
)
const (
SessionAttributesDisplayNameClientIPAddress = "Client IP address"
SessionAttributesDisplayNameNetworkInterfaceType = "Network interface type"
SessionAttributesDisplayNameVPNActive = "VPN active"
SessionAttributesDisplayNameSSID = "SSID"
SessionAttributesDisplayNameTLSDDeviceID = "TLS device ID"
SessionAttributesDisplayNameClientDeviceID = "Device ID"
SessionAttributesDisplayNameMDMEnrolled = "MDM enrolled"
SessionAttributesDisplayNameHardwareID = "Hardware ID"
SessionAttributesDisplayNameOSPlatform = "OS platform"
SessionAttributesDisplayNameOSVersion = "OS version"
SessionAttributesDisplayNameClientVersion = "Client version"
SessionAttributesDisplayNameJailbreakDetected = "Jailbreak detected"
SessionAttributesDisplayNameServerFQDN = "Server FQDN"
SessionAttributesDisplayNameClientFQDN = "Client FQDN"
SessionAttributesDisplayNameUserAgentPlatform = "User agent platform"
SessionAttributesDisplayNameUserAgentOS = "User agent OS"
SessionAttributesDisplayNameUserAgentBrowserName = "User agent browser name"
SessionAttributesDisplayNameUserAgentBrowserVersion = "User agent browser version"
SessionAttributesDisplayNameIPAddress = "IP address"
)
const (
SAAttrEnabled = "enabled"
SAAttrPlatforms = "platforms"
SAAttrTTLSeconds = "ttl_seconds"
SAAttrGracePeriodSeconds = "grace_period_seconds"
SAAttrDisplayName = "display_name"
)
const (
SessionAttributeDefaultTTLNetworkIdentity = 15
SessionAttributeDefaultTTLPosture = 60
SessionAttributeDefaultTTLIdentity = 300
)
const (
SessionAttributeDefaultGraceNetworkIdentity = 15
SessionAttributeDefaultGracePosture = 60
SessionAttributeDefaultGraceIdentity = 300
)
const (
SessionAttributeHeaderClientAttributes = "X-MM-Session-Attributes"
SessionAttributeHeaderProxyDeviceID = "X-Mattermost-Session-Attribute-Device-Id"
)
const (
SessionOperatorInCIDR = "inCIDR"
SessionOperatorVersionEQ = "versionEQ"
SessionOperatorVersionGT = "versionGT"
SessionOperatorVersionGTE = "versionGTE"
SessionOperatorVersionLT = "versionLT"
SessionOperatorVersionLTE = "versionLTE"
)
var sessionStringOperators = []string{"==", "!=", "in", "startsWith", "endsWith", "contains"}
var sessionVersionOperators = []string{
SessionOperatorVersionEQ,
SessionOperatorVersionGT,
SessionOperatorVersionGTE,
SessionOperatorVersionLT,
SessionOperatorVersionLTE,
}
func sessionOperators(extra ...string) StringInterface {
return StringInterface{
NativeAttributeAttrOperators: append(slices.Clone(sessionStringOperators), extra...),
}
}
var SessionAttributesRequestDerivedFieldNames = map[string]struct{}{
SessionAttributesPropertyFieldUserAgentPlatform: {},
SessionAttributesPropertyFieldUserAgentOS: {},
SessionAttributesPropertyFieldUserAgentBrowserName: {},
SessionAttributesPropertyFieldUserAgentBrowserVersion: {},
SessionAttributesPropertyFieldIPAddress: {},
}
var SessionAttributesDeviceIDFieldNames = map[string]struct{}{
SessionAttributesPropertyFieldTLSDDeviceID: {},
SessionAttributesPropertyFieldClientDeviceID: {},
SessionAttributesPropertyFieldHardwareID: {},
}
type SessionAttributesClusterPayload struct {
SessionID string `json:"session_id"`
Attrs map[string]any `json:"attrs"`
Timestamp int64 `json:"timestamp"`
}
type SAField struct {
PropertyField
Attrs SAAttrs `json:"attrs"`
}
type SAAttrs struct {
Enabled bool `json:"enabled"`
Platforms []string `json:"platforms"`
TTLSeconds int `json:"ttl_seconds"`
GracePeriodSeconds int `json:"grace_period_seconds"`
DisplayName string `json:"display_name,omitempty"`
}
type SessionAttributeManifestEntry struct {
Name string `json:"name"`
Type string `json:"type"`
TTLSeconds int `json:"ttl_seconds"`
GracePeriodSeconds int `json:"grace_period_seconds"`
Platforms []string `json:"platforms"`
DisplayName string `json:"display_name,omitempty"`
}
func SAFieldFromPropertyField(field *PropertyField) (*SAField, error) {
if field == nil {
return nil, fmt.Errorf("property field is nil")
}
sa := &SAField{PropertyField: *field}
if len(field.Attrs) == 0 {
return sa, nil
}
// The only way we can convert the attrs to SAAttrs is to marshal and unmarshal.
attrsJSON, err := json.Marshal(field.Attrs)
if err != nil {
return nil, fmt.Errorf("marshal session attribute field attrs: %w", err)
}
if err := json.Unmarshal(attrsJSON, &sa.Attrs); err != nil {
return nil, fmt.Errorf("unmarshal session attribute field attrs: %w", err)
}
return sa, nil
}
func (f *SAField) EnabledForPlatform(platform string) bool {
if f == nil || !f.Attrs.Enabled {
return false
}
return slices.Contains(f.Attrs.Platforms, platform)
}
// IsValidSessionAttributeValue checks an incoming session attribute value against the field schema.
func IsValidSessionAttributeValue(field *PropertyField, value any) bool {
if field == nil || value == nil {
return false
}
switch field.Type {
case PropertyFieldTypeText:
str, ok := value.(string)
if !ok {
return false
}
if str == "" {
return false
}
return true
case PropertyFieldTypeSelect:
str, ok := value.(string)
if !ok || str == "" {
return false
}
rawOptions := field.GetAttr(PropertyFieldAttributeOptions)
if rawOptions == nil {
return false
}
options, err := NewPropertyOptionsFromFieldAttrs[*PluginPropertyOption](rawOptions)
if err != nil {
return false
}
for _, option := range options {
if option.GetName() == str || option.GetID() == str {
return true
}
}
return false
default:
return false
}
}
func sessionAttributeFieldAttrs(platforms []string, ttl, grace int) StringInterface {
return StringInterface{
SAAttrEnabled: false,
SAAttrPlatforms: platforms,
SAAttrTTLSeconds: ttl,
SAAttrGracePeriodSeconds: grace,
}
}
func sessionAttributeField(groupID, name, displayName string, fieldType PropertyFieldType, platforms []string, ttl, grace int, extraAttrs StringInterface) *PropertyField {
attrs := sessionAttributeFieldAttrs(platforms, ttl, grace)
attrs[SAAttrDisplayName] = displayName
maps.Copy(attrs, extraAttrs)
return &PropertyField{
GroupID: groupID,
Name: name,
Type: fieldType,
ObjectType: PropertyFieldObjectTypeSession,
TargetType: string(PropertyFieldTargetLevelSystem),
TargetID: "",
PermissionField: NewPointer(PermissionLevelSysadmin),
PermissionValues: NewPointer(PermissionLevelSysadmin),
PermissionOptions: NewPointer(PermissionLevelSysadmin),
Attrs: attrs,
}
}
// SessionAttributeSystemFields returns the built-in session attribute schema fields.
func SessionAttributeSystemFields(groupID string) []*PropertyField {
allPlatforms := []string{SessionAttributePlatformDesktop, SessionAttributePlatformMobile, SessionAttributePlatformBrowser}
clientsOnly := []string{SessionAttributePlatformDesktop, SessionAttributePlatformMobile}
desktopBrowser := []string{SessionAttributePlatformDesktop, SessionAttributePlatformBrowser}
desktopOnly := []string{SessionAttributePlatformDesktop}
mobileOnly := []string{SessionAttributePlatformMobile}
boolSelectOptions := StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "true"},
{"name": "false"},
},
}
return []*PropertyField{
sessionAttributeField(groupID, SessionAttributesPropertyFieldIPAddress, SessionAttributesDisplayNameIPAddress, PropertyFieldTypeText, allPlatforms, SessionAttributeDefaultTTLNetworkIdentity, SessionAttributeDefaultGraceNetworkIdentity, sessionOperators(SessionOperatorInCIDR)),
sessionAttributeField(groupID, SessionAttributesPropertyFieldClientIPAddress, SessionAttributesDisplayNameClientIPAddress, PropertyFieldTypeText, clientsOnly, SessionAttributeDefaultTTLNetworkIdentity, SessionAttributeDefaultGraceNetworkIdentity, sessionOperators(SessionOperatorInCIDR)),
sessionAttributeField(groupID, SessionAttributesPropertyFieldNetworkInterfaceType, SessionAttributesDisplayNameNetworkInterfaceType, PropertyFieldTypeSelect, clientsOnly, SessionAttributeDefaultTTLNetworkIdentity, SessionAttributeDefaultGraceNetworkIdentity, StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "wifi"},
{"name": "ethernet"},
{"name": "cellular"},
{"name": "vpn"},
{"name": "bluetooth"},
{"name": "other"},
},
}),
sessionAttributeField(groupID, SessionAttributesPropertyFieldVPNActive, SessionAttributesDisplayNameVPNActive, PropertyFieldTypeSelect, clientsOnly, SessionAttributeDefaultTTLNetworkIdentity, SessionAttributeDefaultGraceNetworkIdentity, boolSelectOptions),
sessionAttributeField(groupID, SessionAttributesPropertyFieldSSID, SessionAttributesDisplayNameSSID, PropertyFieldTypeText, clientsOnly, SessionAttributeDefaultTTLNetworkIdentity, SessionAttributeDefaultGraceNetworkIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldMDMEnrolled, SessionAttributesDisplayNameMDMEnrolled, PropertyFieldTypeSelect, clientsOnly, SessionAttributeDefaultTTLPosture, SessionAttributeDefaultGracePosture, boolSelectOptions),
sessionAttributeField(groupID, SessionAttributesPropertyFieldJailbreakDetected, SessionAttributesDisplayNameJailbreakDetected, PropertyFieldTypeSelect, mobileOnly, SessionAttributeDefaultTTLPosture, SessionAttributeDefaultGracePosture, boolSelectOptions),
sessionAttributeField(groupID, SessionAttributesPropertyFieldOSPlatform, SessionAttributesDisplayNameOSPlatform, PropertyFieldTypeSelect, clientsOnly, SessionAttributeDefaultTTLPosture, SessionAttributeDefaultGracePosture, StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "macos"},
{"name": "windows"},
{"name": "linux"},
{"name": "ios"},
{"name": "android"},
},
}),
sessionAttributeField(groupID, SessionAttributesPropertyFieldOSVersion, SessionAttributesDisplayNameOSVersion, PropertyFieldTypeText, clientsOnly, SessionAttributeDefaultTTLPosture, SessionAttributeDefaultGracePosture, sessionOperators(sessionVersionOperators...)),
sessionAttributeField(groupID, SessionAttributesPropertyFieldClientVersion, SessionAttributesDisplayNameClientVersion, PropertyFieldTypeText, clientsOnly, SessionAttributeDefaultTTLPosture, SessionAttributeDefaultGracePosture, sessionOperators(sessionVersionOperators...)),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentPlatform, SessionAttributesDisplayNameUserAgentPlatform, PropertyFieldTypeSelect, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "Windows"},
{"name": "Macintosh"},
{"name": "Linux"},
{"name": "Android"},
{"name": "iPad"},
{"name": "iPhone"},
{"name": "iPod"},
{"name": "BlackBerry"},
{"name": "Windows Phone"},
{"name": "Unknown"},
},
}),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentOS, SessionAttributesDisplayNameUserAgentOS, PropertyFieldTypeSelect, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "Windows"},
{"name": "Windows 10"},
{"name": "Windows 8.1"},
{"name": "Windows 8"},
{"name": "Windows 7"},
{"name": "Windows Vista"},
{"name": "Windows XP x64 Edition"},
{"name": "Windows XP"},
{"name": "Windows 2000"},
{"name": "Windows Phone"},
{"name": "Mac OS"},
{"name": "iOS"},
{"name": "Android"},
{"name": "Chrome OS"},
{"name": "Linux"},
{"name": "BlackBerry"},
{"name": "Kindle"},
{"name": "webOS"},
{"name": "Unknown"},
},
}),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentBrowserName, SessionAttributesDisplayNameUserAgentBrowserName, PropertyFieldTypeSelect, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, StringInterface{
PropertyFieldAttributeOptions: []map[string]string{
{"name": "Chrome"},
{"name": "Firefox"},
{"name": "Safari"},
{"name": "Edge"},
{"name": "Internet Explorer"},
{"name": "Opera"},
{"name": "Android"},
{"name": "BlackBerry"},
{"name": "Desktop App"},
{"name": "Mobile App"},
{"name": "mmctl"},
{"name": "Unknown"},
},
}),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentBrowserVersion, SessionAttributesDisplayNameUserAgentBrowserVersion, PropertyFieldTypeText, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, sessionOperators(sessionVersionOperators...)),
sessionAttributeField(groupID, SessionAttributesPropertyFieldTLSDDeviceID, SessionAttributesDisplayNameTLSDDeviceID, PropertyFieldTypeText, desktopBrowser, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldClientDeviceID, SessionAttributesDisplayNameClientDeviceID, PropertyFieldTypeText, mobileOnly, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldHardwareID, SessionAttributesDisplayNameHardwareID, PropertyFieldTypeText, desktopOnly, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldServerFQDN, SessionAttributesDisplayNameServerFQDN, PropertyFieldTypeText, clientsOnly, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldClientFQDN, SessionAttributesDisplayNameClientFQDN, PropertyFieldTypeText, desktopOnly, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
}
}