Skip to content

Commit

Permalink
webrtxc: fix MTX_QUERY not set when reading or publishing (#4138) (#3937
Browse files Browse the repository at this point in the history
)
  • Loading branch information
aler9 committed Jan 11, 2025
1 parent 5077fb2 commit cc13106
Show file tree
Hide file tree
Showing 27 changed files with 545 additions and 418 deletions.
11 changes: 6 additions & 5 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,12 @@ func (a *API) middlewareOrigin(ctx *gin.Context) {
}

func (a *API) middlewareAuth(ctx *gin.Context) {
err := a.AuthManager.Authenticate(&auth.Request{
IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionAPI,
HTTPRequest: ctx.Request,
})
req := &auth.Request{
IP: net.ParseIP(ctx.ClientIP()),
Action: conf.AuthActionAPI,
}
req.FillFromHTTPRequest(ctx.Request)
err := a.AuthManager.Authenticate(req)
if err != nil {
if err.(*auth.Error).AskCredentials { //nolint:errorlint
ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`)
Expand Down
88 changes: 12 additions & 76 deletions internal/auth/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
Expand All @@ -16,7 +15,6 @@ import (

"github.com/MicahParks/keyfunc/v3"
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/golang-jwt/jwt/v5"
Expand All @@ -31,50 +29,6 @@ const (
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.
type Error struct {
Message string
Expand Down Expand Up @@ -171,38 +125,11 @@ func (m *Manager) ReloadInternalUsers(u []conf.AuthInternalUser) {

// Authenticate authenticates a request.
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

switch m.Method {
case conf.AuthMethodInternal:
err = m.authenticateInternal(req, &rtspAuthHeader)
err = m.authenticateInternal(req)

case conf.AuthMethodHTTP:
err = m.authenticateHTTP(req)
Expand All @@ -221,7 +148,16 @@ func (m *Manager) Authenticate(req *Request) error {
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()
defer m.mutex.RUnlock()

Expand Down Expand Up @@ -252,7 +188,7 @@ func (m *Manager) authenticateWithUser(
}

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(
req.RTSPRequest,
string(u.User),
Expand Down
111 changes: 64 additions & 47 deletions internal/auth/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,48 +143,63 @@ func TestAuthInternal(t *testing.T) {
}

func TestAuthInternalRTSPDigest(t *testing.T) {
m := Manager{
Method: conf.AuthMethodInternal,
InternalUsers: []conf.AuthInternalUser{
{
User: "myuser",
Pass: "mypass",
IPs: conf.IPNetworks{mustParseCIDR("127.1.1.1/32")},
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
Path: "mypath",
}},
},
},
HTTPAddress: "",
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5},
}
for _, ca := range []string{"ok", "invalid"} {
t.Run(ca, func(t *testing.T) {
m := Manager{
Method: conf.AuthMethodInternal,
InternalUsers: []conf.AuthInternalUser{
{
User: "myuser",
Pass: "mypass",
IPs: conf.IPNetworks{mustParseCIDR("127.1.1.1/32")},
Permissions: []conf.AuthInternalUserPermission{{
Action: conf.AuthActionPublish,
Path: "mypath",
}},
},
},
HTTPAddress: "",
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5},
}

u, err := base.ParseURL("rtsp://127.0.0.1:8554/mypath")
require.NoError(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,
}

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

s.AddAuthorization(req)
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)

err = m.Authenticate(&Request{
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
RTSPRequest: req,
RTSPNonce: "mynonce",
})
require.NoError(t, err)
if ca == "ok" {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}

func TestAuthInternalCredentialsInBearer(t *testing.T) {
Expand All @@ -205,16 +220,17 @@ func TestAuthInternalCredentialsInBearer(t *testing.T) {
RTSPAuthMethods: []auth.ValidateMethod{auth.ValidateMethodDigestMD5},
}

err := m.Authenticate(&Request{
req := &Request{
IP: net.ParseIP("127.1.1.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
Protocol: ProtocolRTSP,
HTTPRequest: &http.Request{
Header: http.Header{"Authorization": []string{"Bearer myuser:mypass"}},
URL: &url.URL{},
},
}
req.FillFromHTTPRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer myuser:mypass"}},
URL: &url.URL{},
})
err := m.Authenticate(req)
require.NoError(t, err)
}

Expand Down Expand Up @@ -399,16 +415,17 @@ func TestAuthJWT(t *testing.T) {
Query: "param=value&jwt=" + ss,
})
} else {
err = m.Authenticate(&Request{
req := &Request{
IP: net.ParseIP("127.0.0.1"),
Action: conf.AuthActionPublish,
Path: "mypath",
Protocol: ProtocolWebRTC,
HTTPRequest: &http.Request{
Header: http.Header{"Authorization": []string{"Bearer " + ss}},
URL: &url.URL{},
},
}
req.FillFromHTTPRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer " + ss}},
URL: &url.URL{},
})
err = m.Authenticate(req)
}
require.NoError(t, err)
})
Expand Down
82 changes: 82 additions & 0 deletions internal/auth/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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()
}

Check warning on line 21 in internal/auth/request.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/request.go#L18-L21

Added lines #L18 - L21 were not covered by tests
}
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
}

func (r *Request) FillFromRTSPRequest(rt *base.Request) {

Check warning on line 56 in internal/auth/request.go

View workflow job for this annotation

GitHub Actions / golangci_lint

exported: exported method Request.FillFromRTSPRequest should have comment or be unexported (revive)
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

Check warning on line 62 in internal/auth/request.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/request.go#L61-L62

Added lines #L61 - L62 were not covered by tests
} else {
r.User = rtspAuthHeader.Username
}
}
}

func (r *Request) FillFromHTTPRequest(h *http.Request) {

Check warning on line 69 in internal/auth/request.go

View workflow job for this annotation

GitHub Actions / golangci_lint

exported: exported method Request.FillFromHTTPRequest should have comment or be unexported (revive)
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)
}
}
}
Loading

0 comments on commit cc13106

Please sign in to comment.