Files
team/server/public/model/session_attributes.go
zeekayandHanzo Dev 739725fe4c Restore the header names the rebrand renamed
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]>
2026-08-15 02:25:50 -07:00

308 lines
14 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, PropertyFieldTypeText, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentOS, SessionAttributesDisplayNameUserAgentOS, PropertyFieldTypeText, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
sessionAttributeField(groupID, SessionAttributesPropertyFieldUserAgentBrowserName, SessionAttributesDisplayNameUserAgentBrowserName, PropertyFieldTypeText, allPlatforms, SessionAttributeDefaultTTLIdentity, SessionAttributeDefaultGraceIdentity, nil),
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),
}
}