mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-09-27 03:56:15 +08:00
This commit is contained in:
@@ -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"`)
|
||||||
|
@@ -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),
|
||||||
|
@@ -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
84
internal/auth/request.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
96
internal/defs/path_access_request.go
Normal file
96
internal/defs/path_access_request.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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"`)
|
||||||
|
@@ -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"`)
|
||||||
|
@@ -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"`)
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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¶m=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¶m=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,
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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{},
|
||||||
},
|
},
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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{},
|
||||||
},
|
},
|
||||||
|
@@ -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
|
||||||
|
@@ -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{}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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{
|
||||||
|
@@ -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}
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
35
internal/test/path_manager.go
Normal file
35
internal/test/path_manager.go
Normal 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)
|
||||||
|
}
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user