2
2
mirror of https://github.com/m1k1o/neko.git synced 2024-07-24 14:40:50 +12:00

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

@ -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) {

@ -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")

@ -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
// --- // ---

@ -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)

@ -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")
} }

@ -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()

@ -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) { }
err := track.WriteSample(media.Sample(sample))
if err != nil && errors.Is(err, io.ErrClosedPipe) { peer.listener = func(sample types.Sample) {
logger.Warn().Err(err).Msg("pipeline failed to write") if peer.paused {
} return
}, }
err := track.WriteSample(media.Sample(sample))
if err != nil && errors.Is(err, io.ErrClosedPipe) {
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
}

@ -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
} }

@ -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{

@ -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
} }

@ -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

@ -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