webrtxc: fix MTX_QUERY not set when reading or publishing (#4138) (#3937) (#4141)

This commit is contained in:
Alessandro Ros
2025-01-11 17:29:48 +01:00
committed by GitHub
parent 5077fb2238
commit 8f04264fe5
27 changed files with 563 additions and 421 deletions

View File

@@ -286,11 +286,13 @@ func (a *API) middlewareOrigin(ctx *gin.Context) {
} }
func (a *API) middlewareAuth(ctx *gin.Context) { func (a *API) middlewareAuth(ctx *gin.Context) {
err := a.AuthManager.Authenticate(&auth.Request{ req := &auth.Request{
IP: net.ParseIP(ctx.ClientIP()), IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionAPI, Action: conf.AuthActionAPI,
HTTPRequest: ctx.Request, }
}) req.FillFromHTTPRequest(ctx.Request)
err := a.AuthManager.Authenticate(req)
if err != nil { if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint if err.(*auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)

View File

@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
@@ -16,7 +15,6 @@ import (
"github.com/MicahParks/keyfunc/v3" "github.com/MicahParks/keyfunc/v3"
"github.com/bluenviron/gortsplib/v4/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
@@ -31,50 +29,6 @@ const (
jwtRefreshPeriod = 60 * 60 * time.Second jwtRefreshPeriod = 60 * 60 * time.Second
) )
func addJWTFromAuthorization(rawQuery string, auth string) string {
jwt := strings.TrimPrefix(auth, "Bearer ")
if rawQuery != "" {
if v, err := url.ParseQuery(rawQuery); err == nil && v.Get("jwt") == "" {
v.Set("jwt", jwt)
return v.Encode()
}
}
return url.Values{"jwt": []string{jwt}}.Encode()
}
// Protocol is a protocol.
type Protocol string
// protocols.
const (
ProtocolRTSP Protocol = "rtsp"
ProtocolRTMP Protocol = "rtmp"
ProtocolHLS Protocol = "hls"
ProtocolWebRTC Protocol = "webrtc"
ProtocolSRT Protocol = "srt"
)
// Request is an authentication request.
type Request struct {
User string
Pass string
IP net.IP
Action conf.AuthAction
// only for ActionPublish, ActionRead, ActionPlayback
Path string
Protocol Protocol
ID *uuid.UUID
Query string
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
// HTTP only
HTTPRequest *http.Request
}
// Error is a authentication error. // Error is a authentication error.
type Error struct { type Error struct {
Message string Message string
@@ -171,38 +125,11 @@ func (m *Manager) ReloadInternalUsers(u []conf.AuthInternalUser) {
// Authenticate authenticates a request. // Authenticate authenticates a request.
func (m *Manager) Authenticate(req *Request) error { func (m *Manager) Authenticate(req *Request) error {
var rtspAuthHeader headers.Authorization
if req.RTSPRequest != nil {
err := rtspAuthHeader.Unmarshal(req.RTSPRequest.Header["Authorization"])
if err == nil {
if rtspAuthHeader.Method == headers.AuthMethodBasic {
req.User = rtspAuthHeader.BasicUser
req.Pass = rtspAuthHeader.BasicPass
} else { // digest
req.User = rtspAuthHeader.Username
}
}
} else if req.HTTPRequest != nil {
req.User, req.Pass, _ = req.HTTPRequest.BasicAuth()
req.Query = req.HTTPRequest.URL.RawQuery
if h := req.HTTPRequest.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
// support passing username and password through Authorization header
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
req.User = parts[0]
req.Pass = parts[1]
} else {
req.Query = addJWTFromAuthorization(req.Query, h)
}
}
}
var err error var err error
switch m.Method { switch m.Method {
case conf.AuthMethodInternal: case conf.AuthMethodInternal:
err = m.authenticateInternal(req, &rtspAuthHeader) err = m.authenticateInternal(req)
case conf.AuthMethodHTTP: case conf.AuthMethodHTTP:
err = m.authenticateHTTP(req) err = m.authenticateHTTP(req)
@@ -221,7 +148,16 @@ func (m *Manager) Authenticate(req *Request) error {
return nil return nil
} }
func (m *Manager) authenticateInternal(req *Request, rtspAuthHeader *headers.Authorization) error { func (m *Manager) authenticateInternal(req *Request) error {
var rtspAuthHeader *headers.Authorization
if req.RTSPRequest != nil {
var tmp headers.Authorization
err := tmp.Unmarshal(req.RTSPRequest.Header["Authorization"])
if err == nil {
rtspAuthHeader = &tmp
}
}
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
@@ -252,7 +188,7 @@ func (m *Manager) authenticateWithUser(
} }
if u.User != "any" { if u.User != "any" {
if req.RTSPRequest != nil && rtspAuthHeader.Method == headers.AuthMethodDigest { if req.RTSPRequest != nil && rtspAuthHeader != nil && rtspAuthHeader.Method == headers.AuthMethodDigest {
err := auth.Validate( err := auth.Validate(
req.RTSPRequest, req.RTSPRequest,
string(u.User), string(u.User),

View File

@@ -143,48 +143,63 @@ func TestAuthInternal(t *testing.T) {
} }
func TestAuthInternalRTSPDigest(t *testing.T) { func TestAuthInternalRTSPDigest(t *testing.T) {
m := Manager{ for _, ca := range []string{"ok", "invalid"} {
Method: conf.AuthMethodInternal, t.Run(ca, func(t *testing.T) {
InternalUsers: []conf.AuthInternalUser{ m := Manager{
{ Method: conf.AuthMethodInternal,
User: "myuser", InternalUsers: []conf.AuthInternalUser{
Pass: "mypass", {
IPs: conf.IPNetworks{mustParseCIDR("127.1.1.1/32")}, User: "myuser",
Permissions: []conf.AuthInternalUserPermission{{ Pass: "mypass",
Action: conf.AuthActionPublish, IPs: conf.IPNetworks{mustParseCIDR("127.1.1.1/32")},
Path: "mypath", Permissions: []conf.AuthInternalUserPermission{{
}}, Action: conf.AuthActionPublish,
}, Path: "mypath",
}, }},
HTTPAddress: "", },
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5}, },
HTTPAddress: "",
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5},
}
u, err := base.ParseURL("rtsp://127.0.0.1:8554/mypath")
require.NoError(t, err)
req := &base.Request{
Method: "ANNOUNCE",
URL: u,
}
if ca == "ok" {
var s *auth.Sender
s, err = auth.NewSender(
auth.GenerateWWWAuthenticate([]auth.ValidateMethod{auth.ValidateMethodDigestMD5}, "IPCAM", "mynonce"),
"myuser",
"mypass",
)
require.NoError(t, err)
s.AddAuthorization(req)
} else {
req.Header = base.Header{"Authorization": base.HeaderValue{"garbage"}}
}
req1 := &Request{
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
RTSPRequest: req,
RTSPNonce: "mynonce",
}
req1.FillFromRTSPRequest(req)
err = m.Authenticate(req1)
if ca == "ok" {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
} }
u, err := base.ParseURL("rtsp://127.0.0.1:8554/mypath")
require.NoError(t, err)
s, err := auth.NewSender(
auth.GenerateWWWAuthenticate([]auth.ValidateMethod{auth.ValidateMethodDigestMD5}, "IPCAM", "mynonce"),
"myuser",
"mypass",
)
require.NoError(t, err)
req := &base.Request{
Method: "ANNOUNCE",
URL: u,
}
s.AddAuthorization(req)
err = m.Authenticate(&Request{
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
RTSPRequest: req,
RTSPNonce: "mynonce",
})
require.NoError(t, err)
} }
func TestAuthInternalCredentialsInBearer(t *testing.T) { func TestAuthInternalCredentialsInBearer(t *testing.T) {
@@ -205,16 +220,17 @@ func TestAuthInternalCredentialsInBearer(t *testing.T) {
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5}, RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5},
} }
err := m.Authenticate(&Request{ req := &Request{
IP: net.ParseIP("127.1.1.1"), IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish, Action: conf.AuthActionPublish,
Path: "mypath", Path: "mypath",
Protocol: ProtocolRTSP, Protocol: ProtocolRTSP,
HTTPRequest: &http.Request{ }
Header: http.Header{"Authorization": []string{"Bearer myuser:mypass"}}, req.FillFromHTTPRequest(&http.Request{
URL: &url.URL{}, Header: http.Header{"Authorization": []string{"Bearer myuser:mypass"}},
}, URL: &url.URL{},
}) })
err := m.Authenticate(req)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -399,16 +415,17 @@ func TestAuthJWT(t *testing.T) {
Query: "param=value&jwt=" + ss, Query: "param=value&jwt=" + ss,
}) })
} else { } else {
err = m.Authenticate(&Request{ req := &Request{
IP: net.ParseIP("127.0.0.1"), IP: net.ParseIP("127.0.0.1"),
Action: conf.AuthActionPublish, Action: conf.AuthActionPublish,
Path: "mypath", Path: "mypath",
Protocol: ProtocolWebRTC, Protocol: ProtocolWebRTC,
HTTPRequest: &http.Request{ }
Header: http.Header{"Authorization": []string{"Bearer " + ss}}, req.FillFromHTTPRequest(&http.Request{
URL: &url.URL{}, Header: http.Header{"Authorization": []string{"Bearer " + ss}},
}, URL: &url.URL{},
}) })
err = m.Authenticate(req)
} }
require.NoError(t, err) require.NoError(t, err)
}) })

84
internal/auth/request.go Normal file
View File

@@ -0,0 +1,84 @@
package auth
import (
"net"
"net/http"
"net/url"
"strings"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/google/uuid"
)
func addJWTFromAuthorization(rawQuery string, auth string) string {
jwt := strings.TrimPrefix(auth, "Bearer ")
if rawQuery != "" {
if v, err := url.ParseQuery(rawQuery); err == nil && v.Get("jwt") == "" {
v.Set("jwt", jwt)
return v.Encode()
}
}
return url.Values{"jwt": []string{jwt}}.Encode()
}
// Protocol is a protocol.
type Protocol string
// protocols.
const (
ProtocolRTSP Protocol = "rtsp"
ProtocolRTMP Protocol = "rtmp"
ProtocolHLS Protocol = "hls"
ProtocolWebRTC Protocol = "webrtc"
ProtocolSRT Protocol = "srt"
)
// Request is an authentication request.
type Request struct {
User string
Pass string
IP net.IP
Action conf.AuthAction
// only for ActionPublish, ActionRead, ActionPlayback
Path string
Protocol Protocol
ID *uuid.UUID
Query string
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
}
// FillFromRTSPRequest fills User and Pass from a RTSP request.
func (r *Request) FillFromRTSPRequest(rt *base.Request) {
var rtspAuthHeader headers.Authorization
err := rtspAuthHeader.Unmarshal(rt.Header["Authorization"])
if err == nil {
if rtspAuthHeader.Method == headers.AuthMethodBasic {
r.User = rtspAuthHeader.BasicUser
r.Pass = rtspAuthHeader.BasicPass
} else {
r.User = rtspAuthHeader.Username
}
}
}
// FillFromHTTPRequest fills Query, User and Pass from an HTTP request.
func (r *Request) FillFromHTTPRequest(h *http.Request) {
r.Query = h.URL.RawQuery
r.User, r.Pass, _ = h.BasicAuth()
if h := h.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
// support passing user and password through the Authorization header
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
r.User = parts[0]
r.Pass = parts[1]
} else { // move Authorization header to Query
r.Query = addJWTFromAuthorization(r.Query, h)
}
}
}

View File

@@ -2,14 +2,9 @@ package defs
import ( import (
"fmt" "fmt"
"net"
"net/http"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
"github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/stream"
@@ -36,50 +31,6 @@ type Path interface {
RemoveReader(req PathRemoveReaderReq) RemoveReader(req PathRemoveReaderReq)
} }
// PathAccessRequest is a path access request.
type PathAccessRequest struct {
Name string
Query string
Publish bool
SkipAuth bool
// only if skipAuth = false
User string
Pass string
IP net.IP
Proto auth.Protocol
ID *uuid.UUID
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
// HTTP only
HTTPRequest *http.Request
}
// ToAuthRequest converts a path access request into an authentication request.
func (r *PathAccessRequest) ToAuthRequest() *auth.Request {
return &auth.Request{
User: r.User,
Pass: r.Pass,
IP: r.IP,
Action: func() conf.AuthAction {
if r.Publish {
return conf.AuthActionPublish
}
return conf.AuthActionRead
}(),
Path: r.Name,
Protocol: r.Proto,
ID: r.ID,
Query: r.Query,
RTSPRequest: r.RTSPRequest,
RTSPNonce: r.RTSPNonce,
HTTPRequest: r.HTTPRequest,
}
}
// PathFindPathConfRes contains the response of FindPathConf(). // PathFindPathConfRes contains the response of FindPathConf().
type PathFindPathConfRes struct { type PathFindPathConfRes struct {
Conf *conf.Path Conf *conf.Path

View File

@@ -0,0 +1,96 @@
package defs
import (
"net"
"net/http"
"net/url"
"strings"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/google/uuid"
)
func addJWTFromAuthorization(rawQuery string, auth string) string {
jwt := strings.TrimPrefix(auth, "Bearer ")
if rawQuery != "" {
if v, err := url.ParseQuery(rawQuery); err == nil && v.Get("jwt") == "" {
v.Set("jwt", jwt)
return v.Encode()
}
}
return url.Values{"jwt": []string{jwt}}.Encode()
}
// PathAccessRequest is a path access request.
type PathAccessRequest struct {
Name string
Query string
Publish bool
SkipAuth bool
// only if skipAuth = false
User string
Pass string
IP net.IP
Proto auth.Protocol
ID *uuid.UUID
// RTSP only
RTSPRequest *base.Request
RTSPNonce string
}
// ToAuthRequest converts a path access request into an authentication request.
func (r *PathAccessRequest) ToAuthRequest() *auth.Request {
return &auth.Request{
User: r.User,
Pass: r.Pass,
IP: r.IP,
Action: func() conf.AuthAction {
if r.Publish {
return conf.AuthActionPublish
}
return conf.AuthActionRead
}(),
Path: r.Name,
Protocol: r.Proto,
ID: r.ID,
Query: r.Query,
RTSPRequest: r.RTSPRequest,
RTSPNonce: r.RTSPNonce,
}
}
// FillFromRTSPRequest fills User and Pass from a RTSP request.
func (r *PathAccessRequest) FillFromRTSPRequest(rt *base.Request) {
var rtspAuthHeader headers.Authorization
err := rtspAuthHeader.Unmarshal(rt.Header["Authorization"])
if err == nil {
if rtspAuthHeader.Method == headers.AuthMethodBasic {
r.User = rtspAuthHeader.BasicUser
r.Pass = rtspAuthHeader.BasicPass
} else {
r.User = rtspAuthHeader.Username
}
}
}
// FillFromHTTPRequest fills Query, User and Pass from an HTTP request.
func (r *PathAccessRequest) FillFromHTTPRequest(h *http.Request) {
r.Query = h.URL.RawQuery
r.User, r.Pass, _ = h.BasicAuth()
// move Authorization header from headers to Query
if h := h.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
// support passing user and password through the Authorization header
if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 {
r.User = parts[0]
r.Pass = parts[1]
} else {
r.Query = addJWTFromAuthorization(r.Query, h)
}
}
}

View File

@@ -122,11 +122,13 @@ func (m *Metrics) middlewareOrigin(ctx *gin.Context) {
} }
func (m *Metrics) middlewareAuth(ctx *gin.Context) { func (m *Metrics) middlewareAuth(ctx *gin.Context) {
err := m.AuthManager.Authenticate(&auth.Request{ req := &auth.Request{
IP: net.ParseIP(ctx.ClientIP()), IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionMetrics, Action: conf.AuthActionMetrics,
HTTPRequest: ctx.Request, }
}) req.FillFromHTTPRequest(ctx.Request)
err := m.AuthManager.Authenticate(req)
if err != nil { if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint if err.(*auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)

View File

@@ -117,12 +117,14 @@ func (s *Server) middlewareOrigin(ctx *gin.Context) {
} }
func (s *Server) doAuth(ctx *gin.Context, pathName string) bool { func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
err := s.AuthManager.Authenticate(&auth.Request{ req := &auth.Request{
IP: net.ParseIP(ctx.ClientIP()), IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionPlayback, Action: conf.AuthActionPlayback,
Path: pathName, Path: pathName,
HTTPRequest: ctx.Request, }
}) req.FillFromHTTPRequest(ctx.Request)
err := s.AuthManager.Authenticate(req)
if err != nil { if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint if err.(*auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)

View File

@@ -97,11 +97,13 @@ func (pp *PPROF) middlewareOrigin(ctx *gin.Context) {
} }
func (pp *PPROF) middlewareAuth(ctx *gin.Context) { func (pp *PPROF) middlewareAuth(ctx *gin.Context) {
err := pp.AuthManager.Authenticate(&auth.Request{ req := &auth.Request{
IP: net.ParseIP(ctx.ClientIP()), IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionPprof, Action: conf.AuthActionPprof,
HTTPRequest: ctx.Request, }
}) req.FillFromHTTPRequest(ctx.Request)
err := pp.AuthManager.Authenticate(req)
if err != nil { if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint if err.(*auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)

View File

@@ -147,14 +147,16 @@ func (s *httpServer) onRequest(ctx *gin.Context) {
return return
} }
req := defs.PathAccessRequest{
Name: dir,
Publish: false,
IP: net.ParseIP(ctx.ClientIP()),
Proto: auth.ProtocolHLS,
}
req.FillFromHTTPRequest(ctx.Request)
pathConf, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ pathConf, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: dir,
Publish: false,
IP: net.ParseIP(ctx.ClientIP()),
Proto: auth.ProtocolHLS,
HTTPRequest: ctx.Request,
},
}) })
if err != nil { if err != nil {
var terr *auth.Error var terr *auth.Error

View File

@@ -124,8 +124,8 @@ func (m *muxer) runInner() error {
Author: m, Author: m,
AccessRequest: defs.PathAccessRequest{ AccessRequest: defs.PathAccessRequest{
Name: m.pathName, Name: m.pathName,
SkipAuth: true,
Query: m.query, Query: m.query,
SkipAuth: true,
}, },
}) })
if err != nil { if err != nil {

View File

@@ -24,7 +24,7 @@ import (
type dummyPath struct{} type dummyPath struct{}
func (pa *dummyPath) Name() string { func (pa *dummyPath) Name() string {
return "mystream" return "teststream"
} }
func (pa *dummyPath) SafeConf() *conf.Path { func (pa *dummyPath) SafeConf() *conf.Path {
@@ -48,19 +48,6 @@ func (pa *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
func (pa *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { func (pa *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
} }
type dummyPathManager struct {
findPathConf func(req defs.PathFindPathConfReq) (*conf.Path, error)
addReader func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error)
}
func (pm *dummyPathManager) FindPathConf(req defs.PathFindPathConfReq) (*conf.Path, error) {
return pm.findPathConf(req)
}
func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return pm.addReader(req)
}
func TestPreflightRequest(t *testing.T) { func TestPreflightRequest(t *testing.T) {
s := &Server{ s := &Server{
Address: "127.0.0.1:8888", Address: "127.0.0.1:8888",
@@ -103,12 +90,12 @@ func TestServerNotFound(t *testing.T) {
"always remux on", "always remux on",
} { } {
t.Run(ca, func(t *testing.T) { t.Run(ca, func(t *testing.T) {
pm := &dummyPathManager{ pm := &test.PathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "nonexisting", req.AccessRequest.Name) require.Equal(t, "nonexisting", req.AccessRequest.Name)
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "nonexisting", req.AccessRequest.Name) require.Equal(t, "nonexisting", req.AccessRequest.Name)
return nil, nil, fmt.Errorf("not found") return nil, nil, fmt.Errorf("not found")
}, },
@@ -176,13 +163,17 @@ func TestServerRead(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
pm := &dummyPathManager{ pm := &test.PathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "mystream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "mystream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
return &dummyPath{}, str, nil return &dummyPath{}, str, nil
}, },
} }
@@ -210,7 +201,7 @@ func TestServerRead(t *testing.T) {
defer s.Close() defer s.Close()
c := &gohlslib.Client{ c := &gohlslib.Client{
URI: "http://myuser:mypass@127.0.0.1:8888/mystream/index.m3u8", URI: "http://myuser:mypass@127.0.0.1:8888/teststream/index.m3u8?param=value",
} }
recv := make(chan struct{}) recv := make(chan struct{})
@@ -271,13 +262,17 @@ func TestServerRead(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
pm := &dummyPathManager{ pm := &test.PathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "mystream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "mystream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "", req.AccessRequest.Query)
return &dummyPath{}, str, nil return &dummyPath{}, str, nil
}, },
} }
@@ -321,7 +316,7 @@ func TestServerRead(t *testing.T) {
} }
c := &gohlslib.Client{ c := &gohlslib.Client{
URI: "http://myuser:mypass@127.0.0.1:8888/mystream/index.m3u8", URI: "http://myuser:mypass@127.0.0.1:8888/teststream/index.m3u8?param=value",
} }
recv := make(chan struct{}) recv := make(chan struct{})
@@ -371,8 +366,8 @@ func TestDirectory(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
pm := &dummyPathManager{ pm := &test.PathManager{
addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return &dummyPath{}, str, nil return &dummyPath{}, str, nil
}, },
} }
@@ -403,6 +398,6 @@ func TestDirectory(t *testing.T) {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
_, err = os.Stat(filepath.Join(dir, "mydir", "mystream")) _, err = os.Stat(filepath.Join(dir, "mydir", "teststream"))
require.NoError(t, err) require.NoError(t, err)
} }

View File

@@ -10,7 +10,6 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
@@ -63,24 +62,6 @@ func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
} }
type dummyPathManager struct {
path *dummyPath
}
func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) {
if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" {
return nil, &auth.Error{}
}
return pm.path, nil
}
func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" {
return nil, nil, &auth.Error{}
}
return pm.path, pm.path.stream, nil
}
func TestServerPublish(t *testing.T) { func TestServerPublish(t *testing.T) {
for _, encrypt := range []string{ for _, encrypt := range []string{
"plain", "plain",
@@ -105,7 +86,15 @@ func TestServerPublish(t *testing.T) {
streamCreated: make(chan struct{}), streamCreated: make(chan struct{}),
} }
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "user=myuser&pass=mypass&param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:1935", Address: "127.0.0.1:1935",
@@ -205,7 +194,7 @@ func TestServerRead(t *testing.T) {
} }
desc := &description.Session{Medias: []*description.Media{test.MediaH264}} desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New( str, err := stream.New(
512, 512,
1460, 1460,
desc, desc,
@@ -214,9 +203,17 @@ func TestServerRead(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
path := &dummyPath{stream: stream} path := &dummyPath{stream: str}
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "user=myuser&pass=mypass&param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, path.stream, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:1935", Address: "127.0.0.1:1935",
@@ -250,9 +247,9 @@ func TestServerRead(t *testing.T) {
defer nconn.Close() defer nconn.Close()
go func() { go func() {
stream.WaitRunningReader() str.WaitRunningReader()
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
}, },
@@ -261,7 +258,7 @@ func TestServerRead(t *testing.T) {
}, },
}) })
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
PTS: 2 * 90000, PTS: 2 * 90000,
@@ -271,7 +268,7 @@ func TestServerRead(t *testing.T) {
}, },
}) })
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
PTS: 3 * 90000, PTS: 3 * 90000,

View File

@@ -131,16 +131,19 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
} }
} }
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
}
req.FillFromRTSPRequest(ctx.Request)
res := c.pathManager.Describe(defs.PathDescribeReq{ res := c.pathManager.Describe(defs.PathDescribeReq{
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
},
}) })
if res.Err != nil { if res.Err != nil {

View File

@@ -5,10 +5,11 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/auth" rtspauth "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
@@ -63,37 +64,27 @@ func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
} }
type dummyPathManager struct {
path *dummyPath
}
func (pm *dummyPathManager) Describe(_ defs.PathDescribeReq) defs.PathDescribeRes {
return defs.PathDescribeRes{
Path: pm.path,
Stream: pm.path.stream,
Redirect: "",
Err: nil,
}
}
func (pm *dummyPathManager) AddPublisher(_ defs.PathAddPublisherReq) (defs.Path, error) {
return pm.path, nil
}
func (pm *dummyPathManager) AddReader(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return pm.path, pm.path.stream, nil
}
func TestServerPublish(t *testing.T) { func TestServerPublish(t *testing.T) {
path := &dummyPath{ path := &dummyPath{
streamCreated: make(chan struct{}), streamCreated: make(chan struct{}),
} }
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
if req.AccessRequest.User == "" && req.AccessRequest.Pass == "" {
return nil, &auth.Error{Message: "", AskCredentials: true}
}
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:8557", Address: "127.0.0.1:8557",
AuthMethods: []auth.ValidateMethod{auth.ValidateMethodBasic}, AuthMethods: []rtspauth.ValidateMethod{rtspauth.ValidateMethodBasic},
ReadTimeout: conf.Duration(10 * time.Second), ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second), WriteTimeout: conf.Duration(10 * time.Second),
WriteQueueSize: 512, WriteQueueSize: 512,
@@ -172,7 +163,7 @@ func TestServerPublish(t *testing.T) {
func TestServerRead(t *testing.T) { func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}} desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New( str, err := stream.New(
512, 512,
1460, 1460,
desc, desc,
@@ -181,13 +172,37 @@ func TestServerRead(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
path := &dummyPath{stream: stream} path := &dummyPath{stream: str}
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
DescribeImpl: func(req defs.PathDescribeReq) defs.PathDescribeRes {
if req.AccessRequest.User == "" && req.AccessRequest.Pass == "" {
return defs.PathDescribeRes{Err: &auth.Error{Message: "", AskCredentials: true}}
}
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return defs.PathDescribeRes{
Path: path,
Stream: path.stream,
Redirect: "",
Err: nil,
}
},
AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, path.stream, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:8557", Address: "127.0.0.1:8557",
AuthMethods: []auth.ValidateMethod{auth.ValidateMethodBasic}, AuthMethods: []rtspauth.ValidateMethod{rtspauth.ValidateMethodBasic},
ReadTimeout: conf.Duration(10 * time.Second), ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second), WriteTimeout: conf.Duration(10 * time.Second),
WriteQueueSize: 512, WriteQueueSize: 512,
@@ -256,7 +271,7 @@ func TestServerRead(t *testing.T) {
_, err = reader.Play(nil) _, err = reader.Play(nil)
require.NoError(t, err) require.NoError(t, err)
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
}, },

View File

@@ -111,18 +111,21 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
} }
} }
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
Publish: true,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
}
req.FillFromRTSPRequest(ctx.Request)
path, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{ path, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: s, Author: s,
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: ctx.Path,
Query: ctx.Query,
Publish: true,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
},
}) })
if err != nil { if err != nil {
var terr *auth.Error var terr *auth.Error
@@ -182,17 +185,20 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
} }
} }
req := defs.PathAccessRequest{
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
}
req.FillFromRTSPRequest(ctx.Request)
path, stream, err := s.pathManager.AddReader(defs.PathAddReaderReq{ path, stream, err := s.pathManager.AddReader(defs.PathAddReaderReq{
Author: s, Author: s,
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: ctx.Path,
Query: ctx.Query,
IP: c.ip(),
Proto: auth.ProtocolRTSP,
ID: &c.uuid,
RTSPRequest: ctx.Request,
RTSPNonce: c.authNonce,
},
}) })
if err != nil { if err != nil {
var terr *auth.Error var terr *auth.Error

View File

@@ -141,13 +141,13 @@ func (c *conn) runPublish(streamID *streamID) error {
Author: c, Author: c,
AccessRequest: defs.PathAccessRequest{ AccessRequest: defs.PathAccessRequest{
Name: streamID.path, Name: streamID.path,
Query: streamID.query,
IP: c.ip(), IP: c.ip(),
Publish: true, Publish: true,
User: streamID.user, User: streamID.user,
Pass: streamID.pass, Pass: streamID.pass,
Proto: auth.ProtocolSRT, Proto: auth.ProtocolSRT,
ID: &c.uuid, ID: &c.uuid,
Query: streamID.query,
}, },
}) })
if err != nil { if err != nil {
@@ -241,12 +241,12 @@ func (c *conn) runRead(streamID *streamID) error {
Author: c, Author: c,
AccessRequest: defs.PathAccessRequest{ AccessRequest: defs.PathAccessRequest{
Name: streamID.path, Name: streamID.path,
Query: streamID.query,
IP: c.ip(), IP: c.ip(),
User: streamID.user, User: streamID.user,
Pass: streamID.pass, Pass: streamID.pass,
Proto: auth.ProtocolSRT, Proto: auth.ProtocolSRT,
ID: &c.uuid, ID: &c.uuid,
Query: streamID.query,
}, },
}) })
if err != nil { if err != nil {

View File

@@ -7,7 +7,6 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/bluenviron/mediamtx/internal/auth"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
@@ -60,24 +59,6 @@ func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
} }
type dummyPathManager struct {
path *dummyPath
}
func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) {
if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" {
return nil, &auth.Error{}
}
return pm.path, nil
}
func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" {
return nil, nil, &auth.Error{}
}
return pm.path, pm.path.stream, nil
}
func TestServerPublish(t *testing.T) { func TestServerPublish(t *testing.T) {
externalCmdPool := externalcmd.NewPool() externalCmdPool := externalcmd.NewPool()
defer externalCmdPool.Close() defer externalCmdPool.Close()
@@ -86,7 +67,15 @@ func TestServerPublish(t *testing.T) {
streamCreated: make(chan struct{}), streamCreated: make(chan struct{}),
} }
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:8890", Address: "127.0.0.1:8890",
@@ -105,7 +94,7 @@ func TestServerPublish(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
u := "srt://127.0.0.1:8890?streamid=publish:mypath:myuser:mypass" u := "srt://127.0.0.1:8890?streamid=publish:teststream:myuser:mypass:param=value"
srtConf := srt.DefaultConfig() srtConf := srt.DefaultConfig()
address, err := srtConf.UnmarshalURL(u) address, err := srtConf.UnmarshalURL(u)
@@ -176,7 +165,7 @@ func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}} desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
stream, err := stream.New( str, err := stream.New(
512, 512,
1460, 1460,
desc, desc,
@@ -185,9 +174,17 @@ func TestServerRead(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
path := &dummyPath{stream: stream} path := &dummyPath{stream: str}
pathManager := &dummyPathManager{path: path} pathManager := &test.PathManager{
AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, path.stream, nil
},
}
s := &Server{ s := &Server{
Address: "127.0.0.1:8890", Address: "127.0.0.1:8890",
@@ -206,7 +203,7 @@ func TestServerRead(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
u := "srt://127.0.0.1:8890?streamid=read:mypath:myuser:mypass" u := "srt://127.0.0.1:8890?streamid=read:teststream:myuser:mypass:param=value"
srtConf := srt.DefaultConfig() srtConf := srt.DefaultConfig()
address, err := srtConf.UnmarshalURL(u) address, err := srtConf.UnmarshalURL(u)
@@ -219,9 +216,9 @@ func TestServerRead(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer reader.Close() defer reader.Close()
stream.WaitRunningReader() str.WaitRunningReader()
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
}, },
@@ -252,7 +249,7 @@ func TestServerRead(t *testing.T) {
return nil return nil
}) })
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Time{}, NTP: time.Time{},
}, },

View File

@@ -124,14 +124,16 @@ func (s *httpServer) close() {
} }
func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool { func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool {
req := defs.PathAccessRequest{
Name: pathName,
Publish: publish,
IP: net.ParseIP(ctx.ClientIP()),
Proto: auth.ProtocolWebRTC,
}
req.FillFromHTTPRequest(ctx.Request)
_, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ _, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: pathName,
Publish: publish,
IP: net.ParseIP(ctx.ClientIP()),
Proto: auth.ProtocolWebRTC,
HTTPRequest: ctx.Request,
},
}) })
if err != nil { if err != nil {
var terr *auth.Error var terr *auth.Error

View File

@@ -76,27 +76,9 @@ func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) {
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) {
} }
type dummyPathManager struct {
findPathConf func(req defs.PathFindPathConfReq) (*conf.Path, error)
addPublisher func(req defs.PathAddPublisherReq) (defs.Path, error)
addReader func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error)
}
func (pm *dummyPathManager) FindPathConf(req defs.PathFindPathConfReq) (*conf.Path, error) {
return pm.findPathConf(req)
}
func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) {
return pm.addPublisher(req)
}
func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return pm.addReader(req)
}
func initializeTestServer(t *testing.T) *Server { func initializeTestServer(t *testing.T) *Server {
pm := &dummyPathManager{ pm := &test.PathManager{
findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(_ defs.PathFindPathConfReq) (*conf.Path, error) {
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
} }
@@ -179,8 +161,8 @@ func TestPreflightRequest(t *testing.T) {
} }
func TestServerOptionsICEServer(t *testing.T) { func TestServerOptionsICEServer(t *testing.T) {
pathManager := &dummyPathManager{ pathManager := &test.PathManager{
findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(_ defs.PathFindPathConfReq) (*conf.Path, error) {
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
} }
@@ -242,13 +224,19 @@ func TestServerPublish(t *testing.T) {
streamCreated: make(chan struct{}), streamCreated: make(chan struct{}),
} }
pathManager := &dummyPathManager{ pathManager := &test.PathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "teststream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addPublisher: func(req defs.PathAddPublisherReq) (defs.Path, error) { AddPublisherImpl: func(req defs.PathAddPublisherReq) (defs.Path, error) {
require.Equal(t, "teststream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, nil return path, nil
}, },
} }
@@ -523,13 +511,19 @@ func TestServerRead(t *testing.T) {
path := &dummyPath{stream: str} path := &dummyPath{stream: str}
pathManager := &dummyPathManager{ pathManager := &test.PathManager{
findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(req defs.PathFindPathConfReq) (*conf.Path, error) {
require.Equal(t, "teststream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
require.Equal(t, "teststream", req.AccessRequest.Name) require.Equal(t, "teststream", req.AccessRequest.Name)
require.Equal(t, "param=value", req.AccessRequest.Query)
require.Equal(t, "myuser", req.AccessRequest.User)
require.Equal(t, "mypass", req.AccessRequest.Pass)
return path, str, nil return path, str, nil
}, },
} }
@@ -613,11 +607,11 @@ func TestServerRead(t *testing.T) {
} }
func TestServerReadNotFound(t *testing.T) { func TestServerReadNotFound(t *testing.T) {
pm := &dummyPathManager{ pm := &test.PathManager{
findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { FindPathConfImpl: func(_ defs.PathFindPathConfReq) (*conf.Path, error) {
return &conf.Path{}, nil return &conf.Path{}, nil
}, },
addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { AddReaderImpl: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return nil, nil, defs.PathNoOnePublishingError{} return nil, nil, defs.PathNoOnePublishingError{}
}, },
} }

View File

@@ -126,16 +126,18 @@ func (s *session) runInner2() (int, error) {
func (s *session) runPublish() (int, error) { func (s *session) runPublish() (int, error) {
ip, _, _ := net.SplitHostPort(s.req.remoteAddr) ip, _, _ := net.SplitHostPort(s.req.remoteAddr)
req := defs.PathAccessRequest{
Name: s.req.pathName,
Publish: true,
IP: net.ParseIP(ip),
Proto: auth.ProtocolWebRTC,
ID: &s.uuid,
}
req.FillFromHTTPRequest(s.req.httpRequest)
path, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{ path, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: s, Author: s,
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: s.req.pathName,
Publish: true,
IP: net.ParseIP(ip),
Proto: auth.ProtocolWebRTC,
ID: &s.uuid,
HTTPRequest: s.req.httpRequest,
},
}) })
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
@@ -237,15 +239,17 @@ func (s *session) runPublish() (int, error) {
func (s *session) runRead() (int, error) { func (s *session) runRead() (int, error) {
ip, _, _ := net.SplitHostPort(s.req.remoteAddr) ip, _, _ := net.SplitHostPort(s.req.remoteAddr)
req := defs.PathAccessRequest{
Name: s.req.pathName,
IP: net.ParseIP(ip),
Proto: auth.ProtocolWebRTC,
ID: &s.uuid,
}
req.FillFromHTTPRequest(s.req.httpRequest)
path, stream, err := s.pathManager.AddReader(defs.PathAddReaderReq{ path, stream, err := s.pathManager.AddReader(defs.PathAddReaderReq{
Author: s, Author: s,
AccessRequest: defs.PathAccessRequest{ AccessRequest: req,
Name: s.req.pathName,
IP: net.ParseIP(ip),
Proto: auth.ProtocolWebRTC,
ID: &s.uuid,
HTTPRequest: s.req.httpRequest,
},
}) })
if err != nil { if err != nil {
var terr2 defs.PathNoOnePublishingError var terr2 defs.PathNoOnePublishingError

View File

@@ -2,7 +2,7 @@ package test
import "github.com/bluenviron/mediamtx/internal/auth" import "github.com/bluenviron/mediamtx/internal/auth"
// AuthManager is a test auth manager. // AuthManager is a dummy auth manager.
type AuthManager struct { type AuthManager struct {
fnc func(req *auth.Request) error fnc func(req *auth.Request) error
} }

View File

@@ -5,7 +5,7 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
) )
// FormatH264 is a test H264 format. // FormatH264 is a dummy H264 format.
var FormatH264 = &format.H264{ var FormatH264 = &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
@@ -17,7 +17,7 @@ var FormatH264 = &format.H264{
PacketizationMode: 1, PacketizationMode: 1,
} }
// FormatH265 is a test H265 format. // FormatH265 is a dummy H265 format.
var FormatH265 = &format.H265{ var FormatH265 = &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: []byte{ VPS: []byte{
@@ -40,7 +40,7 @@ var FormatH265 = &format.H265{
}, },
} }
// FormatMPEG4Audio is a test MPEG-4 audio format. // FormatMPEG4Audio is a dummy MPEG-4 audio format.
var FormatMPEG4Audio = &format.MPEG4Audio{ var FormatMPEG4Audio = &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{

View File

@@ -18,7 +18,7 @@ func (l *testLogger) Log(level logger.Level, format string, args ...interface{})
l.cb(level, format, args...) l.cb(level, format, args...)
} }
// Logger returns a test logger. // Logger returns a dummy logger.
func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer { func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer {
return &testLogger{cb: cb} return &testLogger{cb: cb}
} }

View File

@@ -5,13 +5,13 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
) )
// MediaH264 is a test H264 media. // MediaH264 is a dummy H264 media.
var MediaH264 = UniqueMediaH264() var MediaH264 = UniqueMediaH264()
// MediaMPEG4Audio is a test MPEG-4 audio media. // MediaMPEG4Audio is a dummy MPEG-4 audio media.
var MediaMPEG4Audio = UniqueMediaMPEG4Audio() var MediaMPEG4Audio = UniqueMediaMPEG4Audio()
// UniqueMediaH264 is a test H264 media. // UniqueMediaH264 is a dummy H264 media.
func UniqueMediaH264() *description.Media { func UniqueMediaH264() *description.Media {
return &description.Media{ return &description.Media{
Type: description.MediaTypeVideo, Type: description.MediaTypeVideo,
@@ -19,7 +19,7 @@ func UniqueMediaH264() *description.Media {
} }
} }
// UniqueMediaMPEG4Audio is a test MPEG-4 audio media. // UniqueMediaMPEG4Audio is a dummy MPEG-4 audio media.
func UniqueMediaMPEG4Audio() *description.Media { func UniqueMediaMPEG4Audio() *description.Media {
return &description.Media{ return &description.Media{
Type: description.MediaTypeAudio, Type: description.MediaTypeAudio,

View File

@@ -0,0 +1,35 @@
package test
import (
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/stream"
)
// PathManager is a dummy path manager.
type PathManager struct {
FindPathConfImpl func(req defs.PathFindPathConfReq) (*conf.Path, error)
DescribeImpl func(req defs.PathDescribeReq) defs.PathDescribeRes
AddPublisherImpl func(req defs.PathAddPublisherReq) (defs.Path, error)
AddReaderImpl func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error)
}
// FindPathConf implements PathManager.
func (pm *PathManager) FindPathConf(req defs.PathFindPathConfReq) (*conf.Path, error) {
return pm.FindPathConfImpl(req)
}
// Describe implements PathManager.
func (pm *PathManager) Describe(req defs.PathDescribeReq) defs.PathDescribeRes {
return pm.DescribeImpl(req)
}
// AddPublisher implements PathManager.
func (pm *PathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) {
return pm.AddPublisherImpl(req)
}
// AddReader implements PathManager.
func (pm *PathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) {
return pm.AddReaderImpl(req)
}

View File

@@ -1,6 +1,6 @@
package test package test
// TLSCertPub is the public key of a test certificate. // TLSCertPub is the public key of a dummy certificate.
var TLSCertPub = []byte(`-----BEGIN CERTIFICATE----- var TLSCertPub = []byte(`-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
@@ -24,7 +24,7 @@ XQxaORfgM//NzX9LhUPk
-----END CERTIFICATE----- -----END CERTIFICATE-----
`) `)
// TLSCertKey is the private key of a test certificate. // TLSCertKey is the private key of a dummy certificate.
var TLSCertKey = []byte(`-----BEGIN RSA PRIVATE KEY----- var TLSCertKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAxvA8skudfNdBrBsIFq+a4ySuzhNZE0y0jRBCmsfAY8zsoms/ MIIEogIBAAKCAQEAxvA8skudfNdBrBsIFq+a4ySuzhNZE0y0jRBCmsfAY8zsoms/
KwNiRpdlVBKYtlIw6/Qpu53fmCBFbPdSCATm+yxqjmwYdZBhd7uWmcS5LzSRPh6y KwNiRpdlVBKYtlIw6/Qpu53fmCBFbPdSCATm+yxqjmwYdZBhd7uWmcS5LzSRPh6y
@@ -54,7 +54,7 @@ y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
`) `)
// TLSCertPubAlt is the public key of an alternative test certificate. // TLSCertPubAlt is the public key of an alternative dummy certificate.
var TLSCertPubAlt = []byte(`-----BEGIN CERTIFICATE----- var TLSCertPubAlt = []byte(`-----BEGIN CERTIFICATE-----
MIIDSTCCAjECFEut6ZxIOnbxi3bhrPLfPQZCLReNMA0GCSqGSIb3DQEBCwUAMGEx MIIDSTCCAjECFEut6ZxIOnbxi3bhrPLfPQZCLReNMA0GCSqGSIb3DQEBCwUAMGEx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
@@ -76,7 +76,7 @@ Qx9nosr5fLwhkx46+B/cotsbI/xPDjLF6RQ1OUpcHwg1HI6czoW4hHn33S0zstCf
BWt5Q1Mb2tGInbmbUgw3wUu/4nWoY+Mq4DKPlKs= BWt5Q1Mb2tGInbmbUgw3wUu/4nWoY+Mq4DKPlKs=
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
// TLSCertKeyAlt is the private key of an alternative test certificate. // TLSCertKeyAlt is the private key of an alternative dummy certificate.
var TLSCertKeyAlt = []byte(`-----BEGIN RSA PRIVATE KEY----- var TLSCertKeyAlt = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEAs37xvXi1ykkwwaDPnIFf04YjUSNiWOnaUFfKykBUOW5JA1Az MIIEoQIBAAKCAQEAs37xvXi1ykkwwaDPnIFf04YjUSNiWOnaUFfKykBUOW5JA1Az
/tgHvyFspYCy15qM7DMI89T7uNmsJhspgbqORw2yOG8TyTUEfaka0dvjL3w4Jmcc /tgHvyFspYCy15qM7DMI89T7uNmsJhspgbqORw2yOG8TyTUEfaka0dvjL3w4Jmcc