mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 19:13:22 +08:00 
			
		
		
		
	This commit is contained in:
		| @@ -1188,7 +1188,7 @@ The JWT is expected to contain a claim, with a list of permissions in the same f | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Clients are expected to pass the JWT in the Authorization header (in case of HLS and WebRTC) or in query parameters (in case of all other protocols), for instance: | ||||
| Clients are expected to pass the JWT in the Authorization header (in case of HLS, WebRTC and all web-based features) or in query parameters (in case of all other protocols), for instance: | ||||
|  | ||||
| ``` | ||||
| ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://localhost:8554/mystream?jwt=MY_JWT | ||||
|   | ||||
| @@ -284,17 +284,13 @@ func (a *API) middlewareOrigin(ctx *gin.Context) { | ||||
| } | ||||
|  | ||||
| func (a *API) middlewareAuth(ctx *gin.Context) { | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
|  | ||||
| 	err := a.AuthManager.Authenticate(&auth.Request{ | ||||
| 		User:   user, | ||||
| 		Pass:   pass, | ||||
| 		Query:  ctx.Request.URL.RawQuery, | ||||
| 		IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 		Action:      conf.AuthActionAPI, | ||||
| 		HTTPRequest: ctx.Request, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if !hasCredentials { | ||||
| 		if err.(*auth.Error).AskCredentials { //nolint:errorlint | ||||
| 			ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 			ctx.AbortWithStatus(http.StatusUnauthorized) | ||||
| 			return | ||||
|   | ||||
| @@ -11,7 +11,6 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bluenviron/mediamtx/internal/auth" | ||||
| 	"github.com/bluenviron/mediamtx/internal/conf" | ||||
| 	"github.com/bluenviron/mediamtx/internal/logger" | ||||
| 	"github.com/bluenviron/mediamtx/internal/test" | ||||
| @@ -111,40 +110,6 @@ func TestPreflightRequest(t *testing.T) { | ||||
| 	require.Equal(t, byts, []byte{}) | ||||
| } | ||||
|  | ||||
| func TestConfigAuth(t *testing.T) { | ||||
| 	cnf := tempConf(t, "api: yes\n") | ||||
|  | ||||
| 	api := API{ | ||||
| 		Address:     "localhost:9997", | ||||
| 		ReadTimeout: conf.StringDuration(10 * time.Second), | ||||
| 		Conf:        cnf, | ||||
| 		AuthManager: &test.AuthManager{ | ||||
| 			Func: func(req *auth.Request) error { | ||||
| 				require.Equal(t, &auth.Request{ | ||||
| 					User:   "myuser", | ||||
| 					Pass:   "mypass", | ||||
| 					IP:     req.IP, | ||||
| 					Action: "api", | ||||
| 					Query:  "key=val", | ||||
| 				}, req) | ||||
| 				return nil | ||||
| 			}, | ||||
| 		}, | ||||
| 		Parent: &testParent{}, | ||||
| 	} | ||||
| 	err := api.Initialize() | ||||
| 	require.NoError(t, err) | ||||
| 	defer api.Close() | ||||
|  | ||||
| 	tr := &http.Transport{} | ||||
| 	defer tr.CloseIdleConnections() | ||||
| 	hc := &http.Client{Transport: tr} | ||||
|  | ||||
| 	var out map[string]interface{} | ||||
| 	httpRequest(t, hc, http.MethodGet, "http://myuser:mypass@localhost:9997/v3/config/global/get?key=val", nil, &out) | ||||
| 	require.Equal(t, true, out["api"]) | ||||
| } | ||||
|  | ||||
| func TestConfigGlobalGet(t *testing.T) { | ||||
| 	cnf := tempConf(t, "api: yes\n") | ||||
|  | ||||
|   | ||||
| @@ -31,6 +31,17 @@ 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 | ||||
|  | ||||
| @@ -55,17 +66,23 @@ type Request struct { | ||||
| 	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 | ||||
| 	AskCredentials bool | ||||
| } | ||||
|  | ||||
| // Error implements the error interface. | ||||
| func (e Error) Error() string { | ||||
| func (e *Error) Error() string { | ||||
| 	return "authentication failed: " + e.Message | ||||
| } | ||||
|  | ||||
| @@ -154,15 +171,6 @@ func (m *Manager) ReloadInternalUsers(u []conf.AuthInternalUser) { | ||||
|  | ||||
| // Authenticate authenticates a request. | ||||
| func (m *Manager) Authenticate(req *Request) error { | ||||
| 	err := m.authenticateInner(req) | ||||
| 	if err != nil { | ||||
| 		return Error{Message: err.Error()} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Manager) authenticateInner(req *Request) error { | ||||
| 	// if this is a RTSP request, fill username and password | ||||
| 	var rtspAuthHeader headers.Authorization | ||||
|  | ||||
| 	if req.RTSPRequest != nil { | ||||
| @@ -175,18 +183,42 @@ func (m *Manager) authenticateInner(req *Request) error { | ||||
| 				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: | ||||
| 		return m.authenticateInternal(req, &rtspAuthHeader) | ||||
| 		err = m.authenticateInternal(req, &rtspAuthHeader) | ||||
|  | ||||
| 	case conf.AuthMethodHTTP: | ||||
| 		return m.authenticateHTTP(req) | ||||
| 		err = m.authenticateHTTP(req) | ||||
|  | ||||
| 	default: | ||||
| 		return m.authenticateJWT(req) | ||||
| 		err = m.authenticateJWT(req) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return &Error{ | ||||
| 			Message:        err.Error(), | ||||
| 			AskCredentials: (req.User == "" && req.Pass == ""), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *Manager) authenticateInternal(req *Request, rtspAuthHeader *headers.Authorization) error { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| @@ -186,6 +187,37 @@ func TestAuthInternalRTSPDigest(t *testing.T) { | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestAuthInternalCredentialsInBearer(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}, | ||||
| 	} | ||||
|  | ||||
| 	err := m.Authenticate(&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{}, | ||||
| 		}, | ||||
| 	}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestAuthHTTP(t *testing.T) { | ||||
| 	for _, outcome := range []string{"ok", "fail"} { | ||||
| 		t.Run(outcome, func(t *testing.T) { | ||||
| @@ -292,6 +324,8 @@ func TestAuthJWT(t *testing.T) { | ||||
| 	// taken from | ||||
| 	// https://github.com/MicahParks/jwkset/blob/master/examples/http_server/main.go | ||||
|  | ||||
| 	for _, ca := range []string{"query", "auth header"} { | ||||
| 		t.Run(ca, func(t *testing.T) { | ||||
| 			key, err := rsa.GenerateKey(rand.Reader, 1024) | ||||
| 			require.NoError(t, err) | ||||
|  | ||||
| @@ -356,14 +390,27 @@ func TestAuthJWT(t *testing.T) { | ||||
| 				JWTClaimKey: "my_permission_key", | ||||
| 			} | ||||
|  | ||||
| 			if ca == "query" { | ||||
| 				err = m.Authenticate(&Request{ | ||||
| 		User:     "", | ||||
| 		Pass:     "", | ||||
| 					IP:       net.ParseIP("127.0.0.1"), | ||||
| 					Action:   conf.AuthActionPublish, | ||||
| 					Path:     "mypath", | ||||
| 					Protocol: ProtocolRTSP, | ||||
| 					Query:    "param=value&jwt=" + ss, | ||||
| 				}) | ||||
| 			} else { | ||||
| 				err = m.Authenticate(&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{}, | ||||
| 					}, | ||||
| 				}) | ||||
| 			} | ||||
| 			require.NoError(t, err) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package defs | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/base" | ||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/description" | ||||
| @@ -35,7 +36,7 @@ type Path interface { | ||||
| 	RemoveReader(req PathRemoveReaderReq) | ||||
| } | ||||
|  | ||||
| // PathAccessRequest is an access request. | ||||
| // PathAccessRequest is a path access request. | ||||
| type PathAccessRequest struct { | ||||
| 	Name     string | ||||
| 	Query    string | ||||
| @@ -43,13 +44,18 @@ type PathAccessRequest struct { | ||||
| 	SkipAuth bool | ||||
|  | ||||
| 	// only if skipAuth = false | ||||
| 	IP          net.IP | ||||
| 	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. | ||||
| @@ -70,6 +76,7 @@ func (r *PathAccessRequest) ToAuthRequest() *auth.Request { | ||||
| 		Query:       r.Query, | ||||
| 		RTSPRequest: r.RTSPRequest, | ||||
| 		RTSPNonce:   r.RTSPNonce, | ||||
| 		HTTPRequest: r.HTTPRequest, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -120,17 +120,13 @@ func (m *Metrics) onRequest(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
|  | ||||
| 	err := m.AuthManager.Authenticate(&auth.Request{ | ||||
| 		User:   user, | ||||
| 		Pass:   pass, | ||||
| 		Query:  ctx.Request.URL.RawQuery, | ||||
| 		IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 		Action:      conf.AuthActionMetrics, | ||||
| 		HTTPRequest: ctx.Request, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if !hasCredentials { | ||||
| 		if err.(*auth.Error).AskCredentials { //nolint:errorlint | ||||
| 			ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 			ctx.AbortWithStatus(http.StatusUnauthorized) | ||||
| 			return | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import ( | ||||
| 	"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" | ||||
| 	"github.com/bluenviron/mediacommon/pkg/formats/fmp4" | ||||
| 	"github.com/bluenviron/mediacommon/pkg/formats/fmp4/seekablebuffer" | ||||
| 	"github.com/bluenviron/mediamtx/internal/auth" | ||||
| 	"github.com/bluenviron/mediamtx/internal/conf" | ||||
| 	"github.com/bluenviron/mediamtx/internal/test" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| @@ -239,19 +238,7 @@ func TestOnGet(t *testing.T) { | ||||
| 						RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"), | ||||
| 					}, | ||||
| 				}, | ||||
| 				AuthManager: &test.AuthManager{ | ||||
| 					Func: func(req *auth.Request) error { | ||||
| 						require.Equal(t, &auth.Request{ | ||||
| 							User:   "myuser", | ||||
| 							Pass:   "mypass", | ||||
| 							IP:     req.IP, | ||||
| 							Action: "playback", | ||||
| 							Path:   "mypath", | ||||
| 							Query:  req.Query, | ||||
| 						}, req) | ||||
| 						return nil | ||||
| 					}, | ||||
| 				}, | ||||
| 				AuthManager: test.NilAuthManager, | ||||
| 				Parent:      test.NilLogger, | ||||
| 			} | ||||
| 			err = s.Initialize() | ||||
|   | ||||
| @@ -9,7 +9,6 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bluenviron/mediamtx/internal/auth" | ||||
| 	"github.com/bluenviron/mediamtx/internal/conf" | ||||
| 	"github.com/bluenviron/mediamtx/internal/test" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| @@ -36,19 +35,7 @@ func TestOnList(t *testing.T) { | ||||
| 				RecordPath: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		AuthManager: &test.AuthManager{ | ||||
| 			Func: func(req *auth.Request) error { | ||||
| 				require.Equal(t, &auth.Request{ | ||||
| 					User:   "myuser", | ||||
| 					Pass:   "mypass", | ||||
| 					IP:     req.IP, | ||||
| 					Action: "playback", | ||||
| 					Query:  "path=mypath", | ||||
| 					Path:   "mypath", | ||||
| 				}, req) | ||||
| 				return nil | ||||
| 			}, | ||||
| 		}, | ||||
| 		AuthManager: test.NilAuthManager, | ||||
| 		Parent:      test.NilLogger, | ||||
| 	} | ||||
| 	err = s.Initialize() | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
| package playback | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| @@ -119,27 +118,21 @@ func (s *Server) middlewareOrigin(ctx *gin.Context) { | ||||
| } | ||||
|  | ||||
| func (s *Server) doAuth(ctx *gin.Context, pathName string) bool { | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
|  | ||||
| 	err := s.AuthManager.Authenticate(&auth.Request{ | ||||
| 		User:   user, | ||||
| 		Pass:   pass, | ||||
| 		Query:  ctx.Request.URL.RawQuery, | ||||
| 		IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 		Action:      conf.AuthActionPlayback, | ||||
| 		Path:        pathName, | ||||
| 		HTTPRequest: ctx.Request, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if !hasCredentials { | ||||
| 		if err.(*auth.Error).AskCredentials { //nolint:errorlint | ||||
| 			ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 			ctx.Writer.WriteHeader(http.StatusUnauthorized) | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		var terr auth.Error | ||||
| 		errors.As(err, &terr) | ||||
|  | ||||
| 		s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Message) | ||||
| 		s.Log(logger.Info, "connection %v failed to authenticate: %v", | ||||
| 			httpp.RemoteAddr(ctx), err.(*auth.Error).Message) //nolint:errorlint | ||||
|  | ||||
| 		// wait some seconds to mitigate brute force attacks | ||||
| 		<-time.After(auth.PauseAfterError) | ||||
|   | ||||
| @@ -92,17 +92,13 @@ func (pp *PPROF) onRequest(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
|  | ||||
| 	err := pp.AuthManager.Authenticate(&auth.Request{ | ||||
| 		User:   user, | ||||
| 		Pass:   pass, | ||||
| 		Query:  ctx.Request.URL.RawQuery, | ||||
| 		IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 		Action:      conf.AuthActionMetrics, | ||||
| 		HTTPRequest: ctx.Request, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if !hasCredentials { | ||||
| 		if err.(*auth.Error).AskCredentials { //nolint:errorlint | ||||
| 			ctx.Writer.Header().Set("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 			ctx.Writer.WriteHeader(http.StatusUnauthorized) | ||||
| 			return | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import ( | ||||
| 	"errors" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	gopath "path" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @@ -37,17 +36,6 @@ func mergePathAndQuery(path string, rawQuery string) string { | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| 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() | ||||
| } | ||||
|  | ||||
| type httpServer struct { | ||||
| 	address        string | ||||
| 	encryption     bool | ||||
| @@ -157,28 +145,19 @@ func (s *httpServer) onRequest(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
|  | ||||
| 	q := ctx.Request.URL.RawQuery | ||||
| 	if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { | ||||
| 		q = addJWTFromAuthorization(q, h) | ||||
| 	} | ||||
|  | ||||
| 	pathConf, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ | ||||
| 		AccessRequest: defs.PathAccessRequest{ | ||||
| 			Name:        dir, | ||||
| 			Query:   q, | ||||
| 			Publish:     false, | ||||
| 			IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 			User:    user, | ||||
| 			Pass:    pass, | ||||
| 			Proto:       auth.ProtocolHLS, | ||||
| 			HTTPRequest: ctx.Request, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			if !hasCredentials { | ||||
| 			if terr.AskCredentials { | ||||
| 				ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 				ctx.Writer.WriteHeader(http.StatusUnauthorized) | ||||
| 				return | ||||
|   | ||||
| @@ -106,8 +106,6 @@ func TestServerNotFound(t *testing.T) { | ||||
| 			pm := &dummyPathManager{ | ||||
| 				findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 					require.Equal(t, "nonexisting", req.AccessRequest.Name) | ||||
| 					require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 					require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 					return &conf.Path{}, nil | ||||
| 				}, | ||||
| 				addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| @@ -181,8 +179,6 @@ func TestServerRead(t *testing.T) { | ||||
| 		pm := &dummyPathManager{ | ||||
| 			findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 				require.Equal(t, "mystream", req.AccessRequest.Name) | ||||
| 				require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 				require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 				return &conf.Path{}, nil | ||||
| 			}, | ||||
| 			addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| @@ -277,8 +273,6 @@ func TestServerRead(t *testing.T) { | ||||
| 		pm := &dummyPathManager{ | ||||
| 			findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 				require.Equal(t, "mystream", req.AccessRequest.Name) | ||||
| 				require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 				require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 				return &conf.Path{}, nil | ||||
| 			}, | ||||
| 			addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| @@ -372,8 +366,7 @@ func TestServerReadAuthorizationHeader(t *testing.T) { | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	pm := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "jwt=testing", req.AccessRequest.Query) | ||||
| 		findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 		addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
|   | ||||
| @@ -168,7 +168,7 @@ func (c *conn) runRead(conn *rtmp.Conn, u *url.URL) error { | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
| @@ -235,7 +235,7 @@ func (c *conn) runPublish(conn *rtmp.Conn, u *url.URL) error { | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
|   | ||||
| @@ -68,14 +68,14 @@ type dummyPathManager struct { | ||||
|  | ||||
| func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) { | ||||
| 	if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" { | ||||
| 		return nil, auth.Error{} | ||||
| 		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 nil, nil, &auth.Error{} | ||||
| 	} | ||||
| 	return pm.path, pm.path.stream, nil | ||||
| } | ||||
|   | ||||
| @@ -139,7 +139,7 @@ func (c *conn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, | ||||
| 	}) | ||||
|  | ||||
| 	if res.Err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(res.Err, &terr) { | ||||
| 			res, err := c.handleAuthError(terr) | ||||
| 			return res, nil, err | ||||
|   | ||||
| @@ -125,7 +125,7 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			return c.handleAuthError(terr) | ||||
| 		} | ||||
| @@ -195,7 +195,7 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx, | ||||
| 			}, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			var terr auth.Error | ||||
| 			var terr *auth.Error | ||||
| 			if errors.As(err, &terr) { | ||||
| 				res, err2 := c.handleAuthError(terr) | ||||
| 				return res, nil, err2 | ||||
|   | ||||
| @@ -151,7 +151,7 @@ func (c *conn) runPublish(streamID *streamID) error { | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
| @@ -250,7 +250,7 @@ func (c *conn) runRead(streamID *streamID) error { | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
|   | ||||
| @@ -66,14 +66,14 @@ type dummyPathManager struct { | ||||
|  | ||||
| func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) { | ||||
| 	if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" { | ||||
| 		return nil, auth.Error{} | ||||
| 		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 nil, nil, &auth.Error{} | ||||
| 	} | ||||
| 	return pm.path, pm.path.stream, nil | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @@ -60,17 +59,6 @@ func sessionLocation(publish bool, path string, secret uuid.UUID) string { | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| 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() | ||||
| } | ||||
|  | ||||
| type httpServer struct { | ||||
| 	address        string | ||||
| 	encryption     bool | ||||
| @@ -120,35 +108,19 @@ func (s *httpServer) close() { | ||||
| } | ||||
|  | ||||
| func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool { | ||||
| 	user, pass, hasCredentials := ctx.Request.BasicAuth() | ||||
| 	q := ctx.Request.URL.RawQuery | ||||
|  | ||||
| 	if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { | ||||
| 		// JWT in authorization bearer -> JWT in query parameters | ||||
| 		q = addJWTFromAuthorization(q, h) | ||||
|  | ||||
| 		// credentials in authorization bearer -> credentials in authorization basic | ||||
| 		if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 { | ||||
| 			user = parts[0] | ||||
| 			pass = parts[1] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	_, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ | ||||
| 		AccessRequest: defs.PathAccessRequest{ | ||||
| 			Name:        pathName, | ||||
| 			Query:   q, | ||||
| 			Publish:     publish, | ||||
| 			IP:          net.ParseIP(ctx.ClientIP()), | ||||
| 			User:    user, | ||||
| 			Pass:    pass, | ||||
| 			Proto:       auth.ProtocolWebRTC, | ||||
| 			HTTPRequest: ctx.Request, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			if !hasCredentials { | ||||
| 			if terr.AskCredentials { | ||||
| 				ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 				ctx.Writer.WriteHeader(http.StatusUnauthorized) | ||||
| 				return false | ||||
| @@ -200,30 +172,31 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	user, pass, _ := ctx.Request.BasicAuth() | ||||
| 	q := ctx.Request.URL.RawQuery | ||||
|  | ||||
| 	if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { | ||||
| 		// JWT in authorization bearer -> JWT in query parameters | ||||
| 		q = addJWTFromAuthorization(q, h) | ||||
|  | ||||
| 		// credentials in authorization bearer -> credentials in authorization basic | ||||
| 		if parts := strings.Split(strings.TrimPrefix(h, "Bearer "), ":"); len(parts) == 2 { | ||||
| 			user = parts[0] | ||||
| 			pass = parts[1] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	res := s.parent.newSession(webRTCNewSessionReq{ | ||||
| 		pathName:    pathName, | ||||
| 		remoteAddr:  httpp.RemoteAddr(ctx), | ||||
| 		query:      q, | ||||
| 		user:       user, | ||||
| 		pass:       pass, | ||||
| 		offer:       offer, | ||||
| 		publish:     publish, | ||||
| 		httpRequest: ctx.Request, | ||||
| 	}) | ||||
| 	if res.err != nil { | ||||
| 		var terr *auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			if terr.AskCredentials { | ||||
| 				ctx.Header("WWW-Authenticate", `Basic realm="mediamtx"`) | ||||
| 				ctx.AbortWithStatus(http.StatusUnauthorized) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			s.Log(logger.Info, "connection %v failed to authenticate: %v", httpp.RemoteAddr(ctx), terr.Message) | ||||
|  | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
|  | ||||
| 			writeError(ctx, http.StatusUnauthorized, terr) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		writeError(ctx, res.errStatusCode, res.err) | ||||
| 		return | ||||
| 	} | ||||
|   | ||||
| @@ -135,11 +135,9 @@ type webRTCNewSessionRes struct { | ||||
| type webRTCNewSessionReq struct { | ||||
| 	pathName    string | ||||
| 	remoteAddr  string | ||||
| 	query      string | ||||
| 	user       string | ||||
| 	pass       string | ||||
| 	offer       []byte | ||||
| 	publish     bool | ||||
| 	httpRequest *http.Request | ||||
| 	res         chan webRTCNewSessionRes | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -96,9 +96,7 @@ func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *st | ||||
|  | ||||
| func initializeTestServer(t *testing.T) *Server { | ||||
| 	pm := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 		findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 	} | ||||
| @@ -182,9 +180,7 @@ func TestPreflightRequest(t *testing.T) { | ||||
|  | ||||
| func TestServerOptionsICEServer(t *testing.T) { | ||||
| 	pathManager := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 		findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 	} | ||||
| @@ -249,14 +245,10 @@ func TestServerPublish(t *testing.T) { | ||||
| 	pathManager := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "teststream", req.AccessRequest.Name) | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 		addPublisher: func(req defs.PathAddPublisherReq) (defs.Path, error) { | ||||
| 			require.Equal(t, "teststream", req.AccessRequest.Name) | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 			return path, nil | ||||
| 		}, | ||||
| 	} | ||||
| @@ -534,14 +526,10 @@ func TestServerRead(t *testing.T) { | ||||
| 			pathManager := &dummyPathManager{ | ||||
| 				findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 					require.Equal(t, "teststream", req.AccessRequest.Name) | ||||
| 					require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 					require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 					return &conf.Path{}, nil | ||||
| 				}, | ||||
| 				addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| 					require.Equal(t, "teststream", req.AccessRequest.Name) | ||||
| 					require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 					require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 					return path, str, nil | ||||
| 				}, | ||||
| 			} | ||||
| @@ -632,167 +620,9 @@ func TestServerRead(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestServerReadAuthorizationBearerJWT(t *testing.T) { | ||||
| 	desc := &description.Session{Medias: []*description.Media{test.MediaH264}} | ||||
|  | ||||
| 	str, err := stream.New( | ||||
| 		512, | ||||
| 		1460, | ||||
| 		desc, | ||||
| 		true, | ||||
| 		test.NilLogger, | ||||
| 	) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	path := &dummyPath{stream: str} | ||||
|  | ||||
| 	pm := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "jwt=testing", req.AccessRequest.Query) | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 		addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| 			require.Equal(t, "jwt=testing", req.AccessRequest.Query) | ||||
| 			return path, str, nil | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	s := &Server{ | ||||
| 		Address:               "127.0.0.1:8886", | ||||
| 		Encryption:            false, | ||||
| 		ServerKey:             "", | ||||
| 		ServerCert:            "", | ||||
| 		AllowOrigin:           "", | ||||
| 		TrustedProxies:        conf.IPNetworks{}, | ||||
| 		ReadTimeout:           conf.StringDuration(10 * time.Second), | ||||
| 		LocalUDPAddress:       "127.0.0.1:8887", | ||||
| 		LocalTCPAddress:       "127.0.0.1:8887", | ||||
| 		IPsFromInterfaces:     true, | ||||
| 		IPsFromInterfacesList: []string{}, | ||||
| 		AdditionalHosts:       []string{}, | ||||
| 		ICEServers:            []conf.WebRTCICEServer{}, | ||||
| 		HandshakeTimeout:      conf.StringDuration(10 * time.Second), | ||||
| 		TrackGatherTimeout:    conf.StringDuration(2 * time.Second), | ||||
| 		ExternalCmdPool:       nil, | ||||
| 		PathManager:           pm, | ||||
| 		Parent:                test.NilLogger, | ||||
| 	} | ||||
| 	err = s.Initialize() | ||||
| 	require.NoError(t, err) | ||||
| 	defer s.Close() | ||||
|  | ||||
| 	tr := &http.Transport{} | ||||
| 	defer tr.CloseIdleConnections() | ||||
| 	hc := &http.Client{Transport: tr} | ||||
|  | ||||
| 	pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{}) | ||||
| 	require.NoError(t, err) | ||||
| 	defer pc.Close() //nolint:errcheck | ||||
|  | ||||
| 	_, err = pc.AddTransceiverFromKind(pwebrtc.RTPCodecTypeVideo) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	offer, err := pc.CreateOffer(nil) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	req, err := http.NewRequest(http.MethodPost, | ||||
| 		"http://localhost:8886/teststream/whep", bytes.NewReader([]byte(offer.SDP))) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	req.Header.Set("Content-Type", "application/sdp") | ||||
| 	req.Header.Set("Authorization", "Bearer testing") | ||||
|  | ||||
| 	res, err := hc.Do(req) | ||||
| 	require.NoError(t, err) | ||||
| 	defer res.Body.Close() | ||||
|  | ||||
| 	require.Equal(t, http.StatusCreated, res.StatusCode) | ||||
| } | ||||
|  | ||||
| func TestServerReadAuthorizationUserPass(t *testing.T) { | ||||
| 	desc := &description.Session{Medias: []*description.Media{test.MediaH264}} | ||||
|  | ||||
| 	str, err := stream.New( | ||||
| 		512, | ||||
| 		1460, | ||||
| 		desc, | ||||
| 		true, | ||||
| 		test.NilLogger, | ||||
| 	) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	path := &dummyPath{stream: str} | ||||
|  | ||||
| 	pm := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 		addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 			return path, str, nil | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	s := &Server{ | ||||
| 		Address:               "127.0.0.1:8886", | ||||
| 		Encryption:            false, | ||||
| 		ServerKey:             "", | ||||
| 		ServerCert:            "", | ||||
| 		AllowOrigin:           "", | ||||
| 		TrustedProxies:        conf.IPNetworks{}, | ||||
| 		ReadTimeout:           conf.StringDuration(10 * time.Second), | ||||
| 		LocalUDPAddress:       "127.0.0.1:8887", | ||||
| 		LocalTCPAddress:       "127.0.0.1:8887", | ||||
| 		IPsFromInterfaces:     true, | ||||
| 		IPsFromInterfacesList: []string{}, | ||||
| 		AdditionalHosts:       []string{}, | ||||
| 		ICEServers:            []conf.WebRTCICEServer{}, | ||||
| 		HandshakeTimeout:      conf.StringDuration(10 * time.Second), | ||||
| 		TrackGatherTimeout:    conf.StringDuration(2 * time.Second), | ||||
| 		ExternalCmdPool:       nil, | ||||
| 		PathManager:           pm, | ||||
| 		Parent:                test.NilLogger, | ||||
| 	} | ||||
| 	err = s.Initialize() | ||||
| 	require.NoError(t, err) | ||||
| 	defer s.Close() | ||||
|  | ||||
| 	tr := &http.Transport{} | ||||
| 	defer tr.CloseIdleConnections() | ||||
| 	hc := &http.Client{Transport: tr} | ||||
|  | ||||
| 	pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{}) | ||||
| 	require.NoError(t, err) | ||||
| 	defer pc.Close() //nolint:errcheck | ||||
|  | ||||
| 	_, err = pc.AddTransceiverFromKind(pwebrtc.RTPCodecTypeVideo) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	offer, err := pc.CreateOffer(nil) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	req, err := http.NewRequest(http.MethodPost, | ||||
| 		"http://localhost:8886/teststream/whep", bytes.NewReader([]byte(offer.SDP))) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	req.Header.Set("Content-Type", "application/sdp") | ||||
| 	req.Header.Set("Authorization", "Bearer myuser:mypass") | ||||
|  | ||||
| 	res, err := hc.Do(req) | ||||
| 	require.NoError(t, err) | ||||
| 	defer res.Body.Close() | ||||
|  | ||||
| 	require.Equal(t, http.StatusCreated, res.StatusCode) | ||||
| } | ||||
|  | ||||
| func TestServerReadNotFound(t *testing.T) { | ||||
| 	pm := &dummyPathManager{ | ||||
| 		findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			require.Equal(t, "myuser", req.AccessRequest.User) | ||||
| 			require.Equal(t, "mypass", req.AccessRequest.Pass) | ||||
| 		findPathConf: func(_ defs.PathFindPathConfReq) (*conf.Path, error) { | ||||
| 			return &conf.Path{}, nil | ||||
| 		}, | ||||
| 		addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { | ||||
|   | ||||
| @@ -130,24 +130,14 @@ func (s *session) runPublish() (int, error) { | ||||
| 		Author: s, | ||||
| 		AccessRequest: defs.PathAccessRequest{ | ||||
| 			Name:        s.req.pathName, | ||||
| 			Query:   s.req.query, | ||||
| 			Publish:     true, | ||||
| 			IP:          net.ParseIP(ip), | ||||
| 			User:    s.req.user, | ||||
| 			Pass:    s.req.pass, | ||||
| 			Proto:       auth.ProtocolWebRTC, | ||||
| 			ID:          &s.uuid, | ||||
| 			HTTPRequest: s.req.httpRequest, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr auth.Error | ||||
| 		if errors.As(err, &terr) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
|  | ||||
| 			return http.StatusUnauthorized, err | ||||
| 		} | ||||
|  | ||||
| 		return http.StatusBadRequest, err | ||||
| 	} | ||||
|  | ||||
| @@ -251,22 +241,13 @@ func (s *session) runRead() (int, error) { | ||||
| 		Author: s, | ||||
| 		AccessRequest: defs.PathAccessRequest{ | ||||
| 			Name:        s.req.pathName, | ||||
| 			Query: s.req.query, | ||||
| 			IP:          net.ParseIP(ip), | ||||
| 			User:  s.req.user, | ||||
| 			Pass:  s.req.pass, | ||||
| 			Proto:       auth.ProtocolWebRTC, | ||||
| 			ID:          &s.uuid, | ||||
| 			HTTPRequest: s.req.httpRequest, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		var terr1 auth.Error | ||||
| 		if errors.As(err, &terr1) { | ||||
| 			// wait some seconds to mitigate brute force attacks | ||||
| 			<-time.After(auth.PauseAfterError) | ||||
| 			return http.StatusUnauthorized, err | ||||
| 		} | ||||
|  | ||||
| 		var terr2 defs.PathNoOnePublishingError | ||||
| 		if errors.As(err, &terr2) { | ||||
| 			return http.StatusNotFound, err | ||||
| @@ -338,7 +319,7 @@ func (s *session) runRead() (int, error) { | ||||
| 		Conf:            path.SafeConf(), | ||||
| 		ExternalCmdEnv:  path.ExternalCmdEnv(), | ||||
| 		Reader:          s.APIReaderDescribe(), | ||||
| 		Query:           s.req.query, | ||||
| 		Query:           s.req.httpRequest.URL.RawQuery, | ||||
| 	}) | ||||
| 	defer onUnreadHook() | ||||
|  | ||||
| @@ -451,7 +432,7 @@ func (s *session) apiItem() *defs.APIWebRTCSession { | ||||
| 			return defs.APIWebRTCSessionStateRead | ||||
| 		}(), | ||||
| 		Path:          s.req.pathName, | ||||
| 		Query:         s.req.query, | ||||
| 		Query:         s.req.httpRequest.URL.RawQuery, | ||||
| 		BytesReceived: bytesReceived, | ||||
| 		BytesSent:     bytesSent, | ||||
| 	} | ||||
|   | ||||
| @@ -4,17 +4,17 @@ import "github.com/bluenviron/mediamtx/internal/auth" | ||||
|  | ||||
| // AuthManager is a test auth manager. | ||||
| type AuthManager struct { | ||||
| 	Func func(req *auth.Request) error | ||||
| 	fnc func(req *auth.Request) error | ||||
| } | ||||
|  | ||||
| // Authenticate replicates auth.Manager.Replicate | ||||
| func (m *AuthManager) Authenticate(req *auth.Request) error { | ||||
| 	return m.Func(req) | ||||
| 	return m.fnc(req) | ||||
| } | ||||
|  | ||||
| // NilAuthManager is an auth manager that accepts everything. | ||||
| var NilAuthManager = &AuthManager{ | ||||
| 	Func: func(_ *auth.Request) error { | ||||
| 	fnc: func(_ *auth.Request) error { | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Alessandro Ros
					Alessandro Ros