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:
362
server/public/model/session.go
Normal file
362
server/public/model/session.go
Normal file
@@ -0,0 +1,362 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionCookieToken = "MMAUTHTOKEN"
|
||||
SessionCookieUser = "MMUSERID"
|
||||
SessionCookieCsrf = "MMCSRF"
|
||||
SessionCookieCloudUrl = "MMCLOUDURL"
|
||||
SessionCacheSize = 35000
|
||||
SessionPropPlatform = "platform"
|
||||
SessionPropOs = "os"
|
||||
SessionPropBrowser = "browser"
|
||||
SessionPropType = "type"
|
||||
SessionPropUserAccessTokenId = "user_access_token_id"
|
||||
SessionPropIsBot = "is_bot"
|
||||
SessionPropIsBotValue = "true"
|
||||
SessionPropOAuthAppID = "oauth_app_id"
|
||||
SessionPropMattermostAppID = "mattermost_app_id"
|
||||
SessionPropLastRemovedDeviceId = "last_removed_device_id"
|
||||
SessionPropLastRemovedVoIPDeviceId = "last_removed_voip_device_id"
|
||||
SessionPropDeviceNotificationDisabled = "device_notification_disabled"
|
||||
SessionPropMobileVersion = "mobile_version"
|
||||
SessionTypeUserAccessToken = "UserAccessToken"
|
||||
SessionTypeCloudKey = "CloudKey"
|
||||
SessionTypeRemoteclusterToken = "RemoteClusterToken"
|
||||
SessionPropIsGuest = "is_guest"
|
||||
SessionActivityTimeout = 1000 * 60 * 5 // 5 minutes
|
||||
SessionUserAccessTokenExpiryHours = 100 * 365 * 24 // 100 years
|
||||
)
|
||||
|
||||
//msgp:tuple StringMap
|
||||
type StringMap map[string]string
|
||||
|
||||
type MobileSessionMetadata struct {
|
||||
Version string
|
||||
Platform string
|
||||
Count float64
|
||||
NotificationDisabled string
|
||||
}
|
||||
|
||||
// LoginOptions carries optional inputs to App.DoLogin. It's a struct rather
|
||||
// than a positional argument list so future additions don't keep changing
|
||||
// the DoLogin signature and rippling through every caller.
|
||||
type LoginOptions struct {
|
||||
DeviceId string
|
||||
VoIPDeviceId string
|
||||
IsMobile bool
|
||||
IsOAuthUser bool
|
||||
IsSaml bool
|
||||
}
|
||||
|
||||
// Session contains the user session details.
|
||||
// This struct's serializer methods are auto-generated. If a new field is added/removed,
|
||||
// please run make gen-serialized.
|
||||
//
|
||||
//msgp:tuple Session
|
||||
type Session struct {
|
||||
Id string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
LastActivityAt int64 `json:"last_activity_at"`
|
||||
UserId string `json:"user_id"`
|
||||
DeviceId string `json:"device_id"`
|
||||
VoIPDeviceId string `json:"voip_device_id"`
|
||||
Roles string `json:"roles"`
|
||||
IsOAuth bool `json:"is_oauth"`
|
||||
ExpiredNotify bool `json:"expired_notify"`
|
||||
Props StringMap `json:"props"`
|
||||
TeamMembers []*TeamMember `json:"team_members" db:"-"`
|
||||
Local bool `json:"local" db:"-"`
|
||||
}
|
||||
|
||||
func (s *Session) Auditable() map[string]any {
|
||||
return map[string]any{
|
||||
"id": s.Id,
|
||||
"create_at": s.CreateAt,
|
||||
"expires_at": s.ExpiresAt,
|
||||
"last_activity_at": s.LastActivityAt,
|
||||
"user_id": s.UserId,
|
||||
"device_id": s.DeviceId,
|
||||
"voip_device_id": s.VoIPDeviceId,
|
||||
"roles": s.Roles,
|
||||
"is_oauth": s.IsOAuth,
|
||||
"expired_notify": s.ExpiredNotify,
|
||||
"local": s.Local,
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnrestricted returns true if the session is unrestricted, which should grant it
|
||||
// with all permissions. This is used for local mode sessions
|
||||
func (s *Session) IsUnrestricted() bool {
|
||||
return s.Local
|
||||
}
|
||||
|
||||
func (s *Session) DeepCopy() *Session {
|
||||
copySession := *s
|
||||
|
||||
if s.Props != nil {
|
||||
copySession.Props = CopyStringMap(s.Props)
|
||||
}
|
||||
|
||||
if s.TeamMembers != nil {
|
||||
copySession.TeamMembers = make([]*TeamMember, len(s.TeamMembers))
|
||||
for index, tm := range s.TeamMembers {
|
||||
copySession.TeamMembers[index] = new(TeamMember)
|
||||
*copySession.TeamMembers[index] = *tm
|
||||
}
|
||||
}
|
||||
|
||||
return ©Session
|
||||
}
|
||||
|
||||
func (s *Session) IsValid() *AppError {
|
||||
if !IsValidId(s.Id) {
|
||||
return NewAppError("Session.IsValid", "model.session.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(s.UserId) {
|
||||
return NewAppError("Session.IsValid", "model.session.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if s.CreateAt == 0 {
|
||||
return NewAppError("Session.IsValid", "model.session.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(s.Roles) > UserRolesMaxLength {
|
||||
return NewAppError("Session.IsValid", "model.session.is_valid.roles_limit.app_error",
|
||||
map[string]any{"Limit": UserRolesMaxLength}, "session_id="+s.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) PreSave() {
|
||||
if s.Id == "" {
|
||||
s.Id = NewId()
|
||||
}
|
||||
|
||||
if s.Token == "" {
|
||||
s.Token = NewId()
|
||||
}
|
||||
|
||||
s.CreateAt = GetMillis()
|
||||
s.LastActivityAt = s.CreateAt
|
||||
|
||||
if s.Props == nil {
|
||||
s.Props = make(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Sanitize() {
|
||||
s.Token = ""
|
||||
}
|
||||
|
||||
func (s *Session) IsExpired() bool {
|
||||
if s.ExpiresAt <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if GetMillis() > s.ExpiresAt {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Session) AddProp(key string, value string) {
|
||||
if s.Props == nil {
|
||||
s.Props = make(map[string]string)
|
||||
}
|
||||
|
||||
s.Props[key] = value
|
||||
}
|
||||
|
||||
func (s *Session) GetTeamByTeamId(teamId string) *TeamMember {
|
||||
for _, tm := range s.TeamMembers {
|
||||
if tm.TeamId == teamId {
|
||||
return tm
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) IsMobileApp() bool {
|
||||
return s.DeviceId != "" || s.IsMobile()
|
||||
}
|
||||
|
||||
func (s *Session) IsMobile() bool {
|
||||
val, ok := s.Props[UserAuthServiceIsMobile]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
isMobile, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
|
||||
return false
|
||||
}
|
||||
return isMobile
|
||||
}
|
||||
|
||||
func (s *Session) IsSaml() bool {
|
||||
val, ok := s.Props[UserAuthServiceIsSaml]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
isSaml, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
|
||||
return false
|
||||
}
|
||||
return isSaml
|
||||
}
|
||||
|
||||
func (s *Session) IsOAuthUser() bool {
|
||||
val, ok := s.Props[UserAuthServiceIsOAuth]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
isOAuthUser, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
|
||||
return false
|
||||
}
|
||||
return isOAuthUser
|
||||
}
|
||||
|
||||
func (s *Session) IsBotUser() bool {
|
||||
val, ok := s.Props[SessionPropIsBot]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if val == SessionPropIsBotValue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Session) IsUserAccessToken() bool {
|
||||
val, ok := s.Props[SessionPropType]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if val == SessionTypeUserAccessToken {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true when session is authenticated as a bot, by personal access token, or is an OAuth app.
|
||||
// Does not indicate other forms of integrations e.g. webhooks, slash commands, etc.
|
||||
func (s *Session) IsIntegration() bool {
|
||||
return s.IsBotUser() || s.IsUserAccessToken() || s.IsOAuth
|
||||
}
|
||||
|
||||
func (s *Session) IsSSOLogin() bool {
|
||||
return s.IsOAuthUser() || s.IsSaml()
|
||||
}
|
||||
|
||||
func (s *Session) IsGuest() bool {
|
||||
val, ok := s.Props[SessionPropIsGuest]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return val == "true"
|
||||
}
|
||||
|
||||
func (s *Session) GetUserRoles() []string {
|
||||
return strings.Fields(s.Roles)
|
||||
}
|
||||
|
||||
func (s *Session) GenerateCSRF() string {
|
||||
token := NewId()
|
||||
s.AddProp("csrf", token)
|
||||
return token
|
||||
}
|
||||
|
||||
func (s *Session) GetCSRF() string {
|
||||
if s.Props == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return s.Props["csrf"]
|
||||
}
|
||||
|
||||
func (s *Session) CreateAt_() float64 {
|
||||
return float64(s.CreateAt)
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt_() float64 {
|
||||
return float64(s.ExpiresAt)
|
||||
}
|
||||
|
||||
func (s *Session) LastActivityAt_() float64 {
|
||||
return float64(s.LastActivityAt)
|
||||
}
|
||||
|
||||
// standardDevicePlatforms is the allowlist for DeviceId.
|
||||
var standardDevicePlatforms = []string{
|
||||
PushNotifyAppleReactNative,
|
||||
PushNotifyAppleReactNative + "beta",
|
||||
PushNotifyAndroidReactNative,
|
||||
}
|
||||
|
||||
// voIPDevicePlatforms is the allowlist for VoIPDeviceId. Android isn't here
|
||||
// yet — once it grows a VoIP equivalent, add it or unify as a single allowlist.
|
||||
var voIPDevicePlatforms = []string{
|
||||
PushNotifyAppleReactNative,
|
||||
PushNotifyAppleReactNative + "beta",
|
||||
}
|
||||
|
||||
// IsValidDeviceId checks that deviceId has the "<platform>[-v<N>]:<token>"
|
||||
// shape and <platform> is in the allowlist. The "-v<N>" suffix is only
|
||||
// stripped when it's terminal and N is a non-negative integer.
|
||||
func IsValidDeviceId(deviceId string, allowed []string) bool {
|
||||
platform, token, ok := strings.Cut(deviceId, ":")
|
||||
if !ok || token == "" {
|
||||
return false
|
||||
}
|
||||
if idx := strings.LastIndex(platform, "-v"); idx != -1 {
|
||||
if v, err := strconv.Atoi(platform[idx+2:]); err == nil && v >= 0 {
|
||||
platform = platform[:idx]
|
||||
}
|
||||
}
|
||||
return slices.Contains(allowed, platform)
|
||||
}
|
||||
|
||||
func IsValidStandardDeviceId(deviceId string) bool {
|
||||
return IsValidDeviceId(deviceId, standardDevicePlatforms)
|
||||
}
|
||||
|
||||
func IsValidVoIPDeviceId(deviceId string) bool {
|
||||
return IsValidDeviceId(deviceId, voIPDevicePlatforms)
|
||||
}
|
||||
|
||||
// RedactDeviceId returns "<platform>:<first-16>…" for safe inclusion in logs.
|
||||
// Returns "" for empty input and the original prefix for malformed input.
|
||||
func RedactDeviceId(deviceId string) string {
|
||||
if deviceId == "" {
|
||||
return ""
|
||||
}
|
||||
platform, token, ok := strings.Cut(deviceId, ":")
|
||||
if !ok || token == "" {
|
||||
return platform
|
||||
}
|
||||
if len(token) <= 16 {
|
||||
return platform + ":" + token
|
||||
}
|
||||
return platform + ":" + token[:16] + "…"
|
||||
}
|
||||
Reference in New Issue
Block a user