private mode implementation.

This commit is contained in:
Miroslav Šedivý 2022-03-26 23:20:38 +01:00
parent f549171ded
commit d004ddd68f
12 changed files with 160 additions and 11 deletions

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/rs/zerolog/log"
"gitlab.com/demodesk/neko/server/pkg/auth" "gitlab.com/demodesk/neko/server/pkg/auth"
"gitlab.com/demodesk/neko/server/pkg/types" "gitlab.com/demodesk/neko/server/pkg/types"
"gitlab.com/demodesk/neko/server/pkg/utils" "gitlab.com/demodesk/neko/server/pkg/utils"
@ -13,6 +15,8 @@ type RoomHandler struct {
sessions types.SessionManager sessions types.SessionManager
desktop types.DesktopManager desktop types.DesktopManager
capture types.CaptureManager capture types.CaptureManager
privateModeImage []byte
} }
func New( func New(
@ -20,13 +24,32 @@ func New(
desktop types.DesktopManager, desktop types.DesktopManager,
capture types.CaptureManager, capture types.CaptureManager,
) *RoomHandler { ) *RoomHandler {
// Init h := &RoomHandler{
return &RoomHandler{
sessions: sessions, sessions: sessions,
desktop: desktop, desktop: desktop,
capture: capture, capture: capture,
} }
// generate fallback image for private mode when needed
sessions.OnPrivateModeChanged(func(isPrivateMode bool) {
if !isPrivateMode {
log.Debug().Msg("clearing private mode fallback image")
h.privateModeImage = nil
return
}
img := h.desktop.GetScreenshotImage()
bytes, err := utils.CreateJPGImage(img, 90)
if err != nil {
log.Err(err).Msg("could not generate private mode fallback image")
return
}
log.Debug().Msg("using private mode fallback image")
h.privateModeImage = bytes
})
return h
} }
func (h *RoomHandler) Route(r types.Router) { func (h *RoomHandler) Route(r types.Router) {

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"gitlab.com/demodesk/neko/server/pkg/auth"
"gitlab.com/demodesk/neko/server/pkg/types" "gitlab.com/demodesk/neko/server/pkg/types"
"gitlab.com/demodesk/neko/server/pkg/types/event" "gitlab.com/demodesk/neko/server/pkg/types/event"
"gitlab.com/demodesk/neko/server/pkg/types/message" "gitlab.com/demodesk/neko/server/pkg/types/message"
@ -81,6 +82,19 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) erro
} }
func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) error { func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) error {
// display fallback image when private mode is enabled even if screencast is not
if session, ok := auth.GetSession(r); ok && session.PrivateModeEnabled() {
if h.privateModeImage != nil {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Content-Type", "image/jpeg")
_, err := w.Write(h.privateModeImage)
return err
}
return utils.HttpBadRequest("private mode is enabled but no fallback image available")
}
screencast := h.capture.Screencast() screencast := h.capture.Screencast()
if !screencast.Enabled() { if !screencast.Enabled() {
return utils.HttpBadRequest("screencast pipeline is not enabled") return utils.HttpBadRequest("screencast pipeline is not enabled")

View File

@ -56,6 +56,9 @@ type SessionManagerCtx struct {
host types.Session host types.Session
hostMu sync.Mutex hostMu sync.Mutex
privateMode bool
privateModeMu sync.Mutex
cursors map[types.Session][]types.Cursor cursors map[types.Session][]types.Cursor
cursorsMu sync.Mutex cursorsMu sync.Mutex
@ -197,6 +200,39 @@ func (manager *SessionManagerCtx) ClearHost() {
manager.SetHost(nil) manager.SetHost(nil)
} }
// ---
// private mode
// ---
func (manager *SessionManagerCtx) SetPrivateMode(isPrivateMode bool) {
manager.privateModeMu.Lock()
// only if value changed
if manager.privateMode == isPrivateMode {
manager.privateModeMu.Unlock()
return
}
// update webrtc paused state for all sessions
for _, session := range manager.List() {
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
webrtcPeer.SetPaused(isPrivateMode && !session.Profile().IsAdmin)
}
}
manager.privateMode = isPrivateMode
manager.privateModeMu.Unlock()
manager.emmiter.Emit("private_mode_changed", isPrivateMode)
}
func (manager *SessionManagerCtx) PrivateMode() bool {
manager.privateModeMu.Lock()
defer manager.privateModeMu.Unlock()
return manager.privateMode
}
// --- // ---
// cursors // cursors
// --- // ---
@ -326,6 +362,12 @@ func (manager *SessionManagerCtx) OnHostChanged(listener func(session types.Sess
}) })
} }
func (manager *SessionManagerCtx) OnPrivateModeChanged(listener func(isPrivateMode bool)) {
manager.emmiter.On("private_mode_changed", func(payload ...interface{}) {
listener(payload[0].(bool))
})
}
// --- // ---
// config // config
// --- // ---

View File

@ -44,6 +44,11 @@ func (session *SessionCtx) profileChanged() {
if (!session.profile.CanConnect || !session.profile.CanLogin) && session.state.IsConnected { if (!session.profile.CanConnect || !session.profile.CanLogin) && session.state.IsConnected {
session.GetWebSocketPeer().Destroy("profile changed") session.GetWebSocketPeer().Destroy("profile changed")
} }
// update webrtc paused state
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
webrtcPeer.SetPaused(session.PrivateModeEnabled())
}
} }
func (session *SessionCtx) State() types.SessionState { func (session *SessionCtx) State() types.SessionState {
@ -54,6 +59,10 @@ func (session *SessionCtx) IsHost() bool {
return session.manager.GetHost() == session return session.manager.GetHost() == session
} }
func (session *SessionCtx) PrivateModeEnabled() bool {
return session.manager.PrivateMode() && !session.profile.IsAdmin
}
func (session *SessionCtx) SetCursor(cursor types.Cursor) { func (session *SessionCtx) SetCursor(cursor types.Cursor) {
if session.manager.InactiveCursors() && session.profile.SendsInactiveCursor { if session.manager.InactiveCursors() && session.profile.SendsInactiveCursor {
session.manager.SetCursor(cursor, session) session.manager.SetCursor(cursor, session)

View File

@ -200,6 +200,12 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
return videoTrack.SetStream(videoStream) return videoTrack.SetStream(videoStream)
}, },
setPaused: func(isPaused bool) {
videoTrack.SetPaused(isPaused)
audioTrack.SetPaused(isPaused)
// TODO: Send fresh cursor position & image when unpausing.
},
iceTrickle: manager.config.ICETrickle, iceTrickle: manager.config.ICETrickle,
} }
@ -314,12 +320,22 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
}) })
cursorImage := func(entry *cursor.ImageEntry) { cursorImage := func(entry *cursor.ImageEntry) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if err := peer.SendCursorImage(entry.Cursor, entry.Image); err != nil { if err := peer.SendCursorImage(entry.Cursor, entry.Image); err != nil {
logger.Err(err).Msg("could not send cursor image") logger.Err(err).Msg("could not send cursor image")
} }
} }
cursorPosition := func(x, y int) { cursorPosition := func(x, y int) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if session.IsHost() { if session.IsHost() {
return return
} }
@ -352,6 +368,11 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
}) })
dataChannel.OnMessage(func(message webrtc.DataChannelMessage) { dataChannel.OnMessage(func(message webrtc.DataChannelMessage) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if err := manager.handle(message.Data, session); err != nil { if err := manager.handle(message.Data, session); err != nil {
logger.Err(err).Msg("data handle failed") logger.Err(err).Msg("data handle failed")
} }

View File

@ -15,6 +15,7 @@ type WebRTCPeerCtx struct {
connection *webrtc.PeerConnection connection *webrtc.PeerConnection
dataChannel *webrtc.DataChannel dataChannel *webrtc.DataChannel
changeVideo func(videoID string) error changeVideo func(videoID string) error
setPaused func(isPaused bool)
iceTrickle bool iceTrickle bool
} }
@ -122,6 +123,19 @@ func (peer *WebRTCPeerCtx) SetVideoID(videoID string) error {
return peer.changeVideo(videoID) return peer.changeVideo(videoID)
} }
func (peer *WebRTCPeerCtx) SetPaused(isPaused bool) error {
peer.mu.Lock()
defer peer.mu.Unlock()
if peer.connection == nil {
return types.ErrWebRTCConnectionNotFound
}
peer.logger.Info().Bool("is_paused", isPaused).Msg("set paused")
peer.setPaused(isPaused)
return nil
}
func (peer *WebRTCPeerCtx) Destroy() { func (peer *WebRTCPeerCtx) Destroy() {
peer.mu.Lock() peer.mu.Lock()
defer peer.mu.Unlock() defer peer.mu.Unlock()

View File

@ -26,12 +26,17 @@ func (manager *WebRTCManagerCtx) newPeerStreamTrack(stream types.StreamSinkManag
peer := &PeerStreamTrack{ peer := &PeerStreamTrack{
logger: logger, logger: logger,
track: track, track: track,
listener: func(sample types.Sample) { }
peer.listener = func(sample types.Sample) {
if peer.paused {
return
}
err := track.WriteSample(media.Sample(sample)) err := track.WriteSample(media.Sample(sample))
if err != nil && errors.Is(err, io.ErrClosedPipe) { if err != nil && errors.Is(err, io.ErrClosedPipe) {
logger.Warn().Err(err).Msg("pipeline failed to write") logger.Warn().Err(err).Msg("pipeline failed to write")
} }
},
} }
err = peer.SetStream(stream) err = peer.SetStream(stream)
@ -41,6 +46,7 @@ func (manager *WebRTCManagerCtx) newPeerStreamTrack(stream types.StreamSinkManag
type PeerStreamTrack struct { type PeerStreamTrack struct {
logger zerolog.Logger logger zerolog.Logger
track *webrtc.TrackLocalStaticSample track *webrtc.TrackLocalStaticSample
paused bool
listener func(sample types.Sample) listener func(sample types.Sample)
stream types.StreamSinkManager stream types.StreamSinkManager
@ -92,3 +98,7 @@ func (peer *PeerStreamTrack) AddToConnection(connection *webrtc.PeerConnection)
return nil return nil
} }
func (peer *PeerStreamTrack) SetPaused(paused bool) {
peer.paused = paused
}

View File

@ -17,7 +17,7 @@ var (
) )
func (h *MessageHandlerCtx) controlRelease(session types.Session) error { func (h *MessageHandlerCtx) controlRelease(session types.Session) error {
if !session.Profile().CanHost { if !session.Profile().CanHost || session.PrivateModeEnabled() {
return ErrIsNotAllowedToHost return ErrIsNotAllowedToHost
} }
@ -32,7 +32,7 @@ func (h *MessageHandlerCtx) controlRelease(session types.Session) error {
} }
func (h *MessageHandlerCtx) controlRequest(session types.Session) error { func (h *MessageHandlerCtx) controlRequest(session types.Session) error {
if !session.Profile().CanHost { if !session.Profile().CanHost || session.PrivateModeEnabled() {
return ErrIsNotAllowedToHost return ErrIsNotAllowedToHost
} }

View File

@ -24,6 +24,11 @@ func (h *MessageHandlerCtx) signalRequest(session types.Session, payload *messag
return err return err
} }
// set webrtc as paused if session has private mode enabled
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil && session.PrivateModeEnabled() {
webrtcPeer.SetPaused(true)
}
session.Send( session.Send(
event.SIGNAL_PROVIDE, event.SIGNAL_PROVIDE,
message.SignalProvide{ message.SignalProvide{

View File

@ -54,6 +54,10 @@ func CanHostOnly(w http.ResponseWriter, r *http.Request) (context.Context, error
return nil, utils.HttpForbidden("session cannot host") return nil, utils.HttpForbidden("session cannot host")
} }
if session.PrivateModeEnabled() {
return nil, utils.HttpUnprocessableEntity("private mode is enabled")
}
return nil, nil return nil, nil
} }

View File

@ -27,6 +27,7 @@ type Session interface {
Profile() MemberProfile Profile() MemberProfile
State() SessionState State() SessionState
IsHost() bool IsHost() bool
PrivateModeEnabled() bool
// cursor // cursor
SetCursor(cursor Cursor) SetCursor(cursor Cursor)
@ -55,6 +56,9 @@ type SessionManager interface {
GetHost() Session GetHost() Session
ClearHost() ClearHost()
SetPrivateMode(isPrivateMode bool)
PrivateMode() bool
SetCursor(cursor Cursor, session Session) SetCursor(cursor Cursor, session Session)
PopCursors() map[Session][]Cursor PopCursors() map[Session][]Cursor
@ -69,6 +73,7 @@ type SessionManager interface {
OnProfileChanged(listener func(session Session)) OnProfileChanged(listener func(session Session))
OnStateChanged(listener func(session Session)) OnStateChanged(listener func(session Session))
OnHostChanged(listener func(session Session)) OnHostChanged(listener func(session Session))
OnPrivateModeChanged(listener func(isPrivateMode bool))
ImplicitHosting() bool ImplicitHosting() bool
InactiveCursors() bool InactiveCursors() bool

View File

@ -26,6 +26,8 @@ type WebRTCPeer interface {
SetCandidate(candidate webrtc.ICECandidateInit) error SetCandidate(candidate webrtc.ICECandidateInit) error
SetVideoID(videoID string) error SetVideoID(videoID string) error
SetPaused(isPaused bool) error
SendCursorPosition(x, y int) error SendCursorPosition(x, y int) error
SendCursorImage(cur *CursorImage, img []byte) error SendCursorImage(cur *CursorImage, img []byte) error