From 0f45aa3f19cf24679874c4d03090d047b2f931f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 21 Apr 2024 20:10:16 +0200 Subject: [PATCH] implement control protection. --- internal/config/session.go | 7 ++++ internal/session/manager.go | 51 ++++++++++++++++++++++++++- internal/websocket/handler/session.go | 31 ++++++++++++++++ pkg/types/session.go | 3 ++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/internal/config/session.go b/internal/config/session.go index 5feae6f..443c02d 100644 --- a/internal/config/session.go +++ b/internal/config/session.go @@ -13,6 +13,7 @@ type Session struct { PrivateMode bool LockedLogins bool LockedControls bool + ControlProtection bool ImplicitHosting bool InactiveCursors bool MercifulReconnect bool @@ -45,6 +46,11 @@ func (Session) Init(cmd *cobra.Command) error { return err } + cmd.PersistentFlags().Bool("session.control_protection", false, "users can gain control only if at least one admin is in the room") + if err := viper.BindPFlag("session.control_protection", cmd.PersistentFlags().Lookup("session.control_protection")); err != nil { + return err + } + cmd.PersistentFlags().Bool("session.implicit_hosting", true, "allow implicit control switching") if err := viper.BindPFlag("session.implicit_hosting", cmd.PersistentFlags().Lookup("session.implicit_hosting")); err != nil { return err @@ -95,6 +101,7 @@ func (s *Session) Set() { s.PrivateMode = viper.GetBool("session.private_mode") s.LockedLogins = viper.GetBool("session.locked_logins") s.LockedControls = viper.GetBool("session.locked_controls") + s.ControlProtection = viper.GetBool("session.control_protection") s.ImplicitHosting = viper.GetBool("session.implicit_hosting") s.InactiveCursors = viper.GetBool("session.inactive_cursors") s.MercifulReconnect = viper.GetBool("session.merciful_reconnect") diff --git a/internal/session/manager.go b/internal/session/manager.go index 3fa9002..7a324b5 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -21,7 +21,8 @@ func New(config *config.Session) *SessionManagerCtx { settings: types.Settings{ PrivateMode: config.PrivateMode, LockedLogins: config.LockedLogins, - LockedControls: config.LockedControls, + LockedControls: config.LockedControls || config.ControlProtection, + ControlProtection: config.ControlProtection, ImplicitHosting: config.ImplicitHosting, InactiveCursors: config.InactiveCursors, MercifulReconnect: config.MercifulReconnect, @@ -194,6 +195,17 @@ func (manager *SessionManagerCtx) List() []types.Session { return sessions } +func (manager *SessionManagerCtx) Range(f func(session types.Session) bool) { + manager.sessionsMu.Lock() + defer manager.sessionsMu.Unlock() + + for _, session := range manager.sessions { + if !f(session) { + return + } + } +} + // --- // host // --- @@ -371,6 +383,23 @@ func (manager *SessionManagerCtx) UpdateSettings(new types.Settings) { manager.settings = new manager.settingsMu.Unlock() + manager.updateSettings(new, old) +} + +func (manager *SessionManagerCtx) UpdateSettingsFunc(f func(settings *types.Settings) bool) { + manager.settingsMu.Lock() + new := manager.settings + if f(&new) { + old := manager.settings + manager.settings = new + manager.settingsMu.Unlock() + manager.updateSettings(new, old) + return + } + manager.settingsMu.Unlock() +} + +func (manager *SessionManagerCtx) updateSettings(new, old types.Settings) { // if private mode changed if old.PrivateMode != new.PrivateMode { // update webrtc paused state for all sessions @@ -389,6 +418,26 @@ func (manager *SessionManagerCtx) UpdateSettings(new types.Settings) { } } + // if control protection changed and controls are not locked + if old.ControlProtection != new.ControlProtection && new.ControlProtection && !new.LockedControls { + // if there is no admin, lock controls + hasAdmin := false + manager.Range(func(session types.Session) bool { + if session.Profile().IsAdmin && session.State().IsConnected { + hasAdmin = true + return false + } + return true + }) + + if !hasAdmin { + manager.settingsMu.Lock() + manager.settings.LockedControls = true + new.LockedControls = true + manager.settingsMu.Unlock() + } + } + // if contols have been locked if old.LockedControls != new.LockedControls && new.LockedControls { // if the host is not admin, it must release controls diff --git a/internal/websocket/handler/session.go b/internal/websocket/handler/session.go index 8d62102..c81f181 100644 --- a/internal/websocket/handler/session.go +++ b/internal/websocket/handler/session.go @@ -37,6 +37,16 @@ func (h *MessageHandlerCtx) SessionConnected(session types.Session) error { if err := h.systemAdmin(session); err != nil { return err } + + // update settings in atomic way + h.sessions.UpdateSettingsFunc(func(settings *types.Settings) bool { + // if control protection & locked controls: unlock controls + if settings.LockedControls && settings.ControlProtection { + settings.LockedControls = false + return true // update settings + } + return false // do not update settings + }) } return h.SessionStateChanged(session) @@ -49,6 +59,27 @@ func (h *MessageHandlerCtx) SessionDisconnected(session types.Session) error { h.sessions.ClearHost() } + if session.Profile().IsAdmin { + hasAdmin := false + h.sessions.Range(func(s types.Session) bool { + if s.Profile().IsAdmin && s.ID() != session.ID() && s.State().IsConnected { + hasAdmin = true + return false + } + return true + }) + + // update settings in atomic way + h.sessions.UpdateSettingsFunc(func(settings *types.Settings) bool { + // if control protection & not locked controls & no admin: lock controls + if !settings.LockedControls && settings.ControlProtection && !hasAdmin { + settings.LockedControls = true + return true // update settings + } + return false // do not update settings + }) + } + return h.SessionStateChanged(session) } diff --git a/pkg/types/session.go b/pkg/types/session.go index 9496eb7..49d7c68 100644 --- a/pkg/types/session.go +++ b/pkg/types/session.go @@ -43,6 +43,7 @@ type Settings struct { PrivateMode bool `json:"private_mode"` LockedLogins bool `json:"locked_logins"` LockedControls bool `json:"locked_controls"` + ControlProtection bool `json:"control_protection"` ImplicitHosting bool `json:"implicit_hosting"` InactiveCursors bool `json:"inactive_cursors"` MercifulReconnect bool `json:"merciful_reconnect"` @@ -80,6 +81,7 @@ type SessionManager interface { Get(id string) (Session, bool) GetByToken(token string) (Session, bool) List() []Session + Range(func(Session) bool) SetHost(host Session) GetHost() (Session, bool) @@ -102,6 +104,7 @@ type SessionManager interface { OnSettingsChanged(listener func(new Settings, old Settings)) UpdateSettings(Settings) + UpdateSettingsFunc(f func(settings *Settings) bool) Settings() Settings CookieEnabled() bool