Merge pull request #157 from strukturag/split-audio-video-permissions

Support separate permissions for publishing audio / video.
This commit is contained in:
Joachim Bauch 2021-11-03 15:39:50 +01:00 committed by GitHub
commit 7628a15e61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 84 additions and 14 deletions

View File

@ -323,15 +323,18 @@ func (m *HelloClientMessage) CheckValid() error {
const (
// Features for all clients.
ServerFeatureMcu = "mcu"
ServerFeatureSimulcast = "simulcast"
ServerFeatureMcu = "mcu"
ServerFeatureSimulcast = "simulcast"
ServerFeatureAudioVideoPermissions = "audio-video-permissions"
// Features for internal clients only.
ServerFeatureInternalVirtualSessions = "virtual-sessions"
)
var (
DefaultFeatures []string
DefaultFeatures []string = []string{
ServerFeatureAudioVideoPermissions,
}
DefaultFeaturesInternal []string = []string{
ServerFeatureInternalVirtualSessions,
}

View File

@ -56,6 +56,7 @@ const (
)
type BackendClient struct {
hub *Hub
transport *http.Transport
version string
backends *BackendConfiguration
@ -342,6 +343,9 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ
req.Header.Set("Accept", "application/json")
req.Header.Set("OCS-APIRequest", "true")
req.Header.Set("User-Agent", "nextcloud-spreed-signaling/"+b.version)
if b.hub != nil {
req.Header.Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", "))
}
// Add checksum so the backend can validate the request.
AddBackendChecksum(req, data, secret)

View File

@ -166,6 +166,7 @@ func (b *BackendServer) Start(r *mux.Router) error {
func (b *BackendServer) setComonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "nextcloud-spreed-signaling/"+b.version)
w.Header().Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", "))
f(w, r)
}
}

View File

@ -175,6 +175,7 @@ func (s *ClientSession) HasFeature(feature string) bool {
return false
}
// HasPermission checks if the session has the passed permissions.
func (s *ClientSession) HasPermission(permission Permission) bool {
s.mu.Lock()
defer s.mu.Unlock()
@ -182,6 +183,23 @@ func (s *ClientSession) HasPermission(permission Permission) bool {
return s.hasPermissionLocked(permission)
}
// HasAnyPermission checks if the session has one of the passed permissions.
func (s *ClientSession) HasAnyPermission(permission ...Permission) bool {
if len(permission) == 0 {
return false
}
s.mu.Lock()
defer s.mu.Unlock()
for _, p := range permission {
if s.hasPermissionLocked(p) {
return true
}
}
return false
}
func (s *ClientSession) hasPermissionLocked(permission Permission) bool {
if !s.supportsPermissions {
// Old-style session that doesn't receive permissions from Nextcloud.

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/nats-io/nats.go v1.13.0
github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0
github.com/oschwald/maxminddb-golang v1.8.0
github.com/pion/sdp v1.3.0
github.com/prometheus/client_golang v1.11.0
go.etcd.io/etcd v0.5.0-alpha.5.0.20210226220824-aa7126864d82
go.uber.org/zap v1.13.0 // indirect

2
go.sum
View File

@ -156,6 +156,8 @@ github.com/notedit/janus-go v0.0.0-20200517101215-10eb8b95d1a0/go.mod h1:BN/Txse
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/pion/sdp v1.3.0 h1:21lpgEILHyolpsIrbCBagZaAPj4o057cFjzaFebkVOs=
github.com/pion/sdp v1.3.0/go.mod h1:ceA2lTyftydQTuCIbUNoH77aAt6CiQJaRpssA4Gee8I=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

61
hub.go
View File

@ -42,6 +42,7 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/gorilla/websocket"
"github.com/pion/sdp"
)
var (
@ -332,6 +333,7 @@ func NewHub(config *goconf.ConfigFile, nats NatsClient, r *mux.Router, version s
geoip: geoip,
geoipOverrides: geoipOverrides,
}
backend.hub = hub
hub.upgrader.CheckOrigin = hub.checkOrigin
r.HandleFunc("/spreed", func(w http.ResponseWriter, r *http.Request) {
hub.serveWs(w, r)
@ -1374,8 +1376,8 @@ func (h *Hub) processMessageMsg(client *Client, message *ClientMessage) {
if recipient != nil {
// The recipient is connected to this instance, no need to go through NATS.
if clientData != nil && clientData.Type == "sendoffer" {
if !isAllowedToSend(session, clientData) {
log.Printf("Session %s is not allowed to send offer for %s, ignoring", session.PublicId(), clientData.RoomType)
if err := isAllowedToSend(session, clientData); err != nil {
log.Printf("Session %s is not allowed to send offer for %s, ignoring (%s)", session.PublicId(), clientData.RoomType, err)
sendNotAllowed(session, message, "Not allowed to send offer")
return
}
@ -1651,14 +1653,51 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) {
}
}
func isAllowedToSend(session *ClientSession, data *MessageClientMessageData) bool {
var permission Permission
func isAllowedToSend(session *ClientSession, data *MessageClientMessageData) error {
if data.RoomType == "screen" {
permission = PERMISSION_MAY_PUBLISH_SCREEN
if session.HasPermission(PERMISSION_MAY_PUBLISH_SCREEN) {
return nil
}
return fmt.Errorf("permission \"%s\" not found", PERMISSION_MAY_PUBLISH_SCREEN)
} else if session.HasPermission(PERMISSION_MAY_PUBLISH_MEDIA) {
// Client is allowed to publish any media (audio / video).
return nil
} else if data != nil && data.Type == "offer" {
// Parse SDP to check what user is trying to publish and check permissions accordingly.
sdpValue, found := data.Payload["sdp"]
if !found {
return fmt.Errorf("offer does not contain a sdp")
}
sdpText, ok := sdpValue.(string)
if !ok {
return fmt.Errorf("offer does not contain a valid sdp")
}
var s sdp.SessionDescription
if err := s.Unmarshal(sdpText); err != nil {
return fmt.Errorf("could not parse sdp: %w", err)
}
for _, md := range s.MediaDescriptions {
switch md.MediaName.Media {
case "audio":
if !session.HasPermission(PERMISSION_MAY_PUBLISH_AUDIO) {
return fmt.Errorf("permission \"%s\" not found", PERMISSION_MAY_PUBLISH_AUDIO)
}
case "video":
if !session.HasPermission(PERMISSION_MAY_PUBLISH_VIDEO) {
return fmt.Errorf("permission \"%s\" not found", PERMISSION_MAY_PUBLISH_VIDEO)
}
}
}
return nil
} else {
permission = PERMISSION_MAY_PUBLISH_MEDIA
// Candidate or unknown event, check if client is allowed to publish any media.
if session.HasAnyPermission(PERMISSION_MAY_PUBLISH_AUDIO, PERMISSION_MAY_PUBLISH_VIDEO) {
return nil
}
return fmt.Errorf("permission check failed")
}
return session.HasPermission(permission)
}
func sendNotAllowed(session *ClientSession, message *ClientMessage, reason string) {
@ -1733,8 +1772,8 @@ func (h *Hub) processMcuMessage(senderSession *ClientSession, session *ClientSes
clientType = "subscriber"
mc, err = session.GetOrCreateSubscriber(ctx, h.mcu, message.Recipient.SessionId, data.RoomType)
case "offer":
if !isAllowedToSend(session, data) {
log.Printf("Session %s is not allowed to offer %s, ignoring", session.PublicId(), data.RoomType)
if err := isAllowedToSend(session, data); err != nil {
log.Printf("Session %s is not allowed to offer %s, ignoring (%s)", session.PublicId(), data.RoomType, err)
sendNotAllowed(senderSession, client_message, "Not allowed to publish.")
return
}
@ -1751,8 +1790,8 @@ func (h *Hub) processMcuMessage(senderSession *ClientSession, session *ClientSes
mc = session.GetSubscriber(message.Recipient.SessionId, data.RoomType)
default:
if session.PublicId() == message.Recipient.SessionId {
if !isAllowedToSend(session, data) {
log.Printf("Session %s is not allowed to send candidate for %s, ignoring", session.PublicId(), data.RoomType)
if err := isAllowedToSend(session, data); err != nil {
log.Printf("Session %s is not allowed to send candidate for %s, ignoring (%s)", session.PublicId(), data.RoomType, err)
sendNotAllowed(senderSession, client_message, "Not allowed to send candidate.")
return
}

View File

@ -31,6 +31,8 @@ type Permission string
var (
PERMISSION_MAY_PUBLISH_MEDIA Permission = "publish-media"
PERMISSION_MAY_PUBLISH_AUDIO Permission = "publish-audio"
PERMISSION_MAY_PUBLISH_VIDEO Permission = "publish-video"
PERMISSION_MAY_PUBLISH_SCREEN Permission = "publish-screen"
PERMISSION_MAY_CONTROL Permission = "control"
)