Add user agent field to RTMP, RTSP, WebRTC, and HLS (#5753)
Surface user agent in the auth HTTP webhook payload as "userAgent" and in the API structs for RTMP, RTSP, WebRTC and HLS connections. --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:
@@ -1213,6 +1213,8 @@ components:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
userAgent:
|
||||
type: string
|
||||
isCDN:
|
||||
type: boolean
|
||||
outboundBytes:
|
||||
@@ -1281,6 +1283,8 @@ components:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
userAgent:
|
||||
type: string
|
||||
inboundBytes:
|
||||
type: integer
|
||||
format: uint64
|
||||
@@ -1376,6 +1380,8 @@ components:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
userAgent:
|
||||
type: string
|
||||
transport:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1774,6 +1780,8 @@ components:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
userAgent:
|
||||
type: string
|
||||
inboundBytes:
|
||||
type: integer
|
||||
format: uint64
|
||||
|
||||
@@ -93,7 +93,8 @@ Each time a user needs to be authenticated, the specified URL will be requested
|
||||
"path": "path",
|
||||
"protocol": "rtsp|rtmp|hls|webrtc|srt",
|
||||
"id": "id",
|
||||
"query": "query"
|
||||
"query": "query",
|
||||
"userAgent": "userAgent"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ func (m *Manager) authenticateHTTP(req *Request, token string) (string, error) {
|
||||
Protocol string `json:"protocol"`
|
||||
ID *uuid.UUID `json:"id"`
|
||||
Query string `json:"query"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
}{
|
||||
IP: req.IP.String(),
|
||||
User: req.Credentials.User,
|
||||
@@ -216,6 +217,7 @@ func (m *Manager) authenticateHTTP(req *Request, token string) (string, error) {
|
||||
Protocol: string(req.Protocol),
|
||||
ID: req.ID,
|
||||
Query: req.Query,
|
||||
UserAgent: req.UserAgent,
|
||||
})
|
||||
|
||||
tr := &http.Transport{
|
||||
|
||||
@@ -282,6 +282,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
ID string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
Query string `json:"query"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
}
|
||||
err := json.NewDecoder(r.Body).Decode(&in)
|
||||
require.NoError(t, err)
|
||||
@@ -293,6 +294,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
in.Protocol != "rtsp" ||
|
||||
(firstReceived && in.ID == "") ||
|
||||
in.Action != "publish" ||
|
||||
in.UserAgent != "testagent" ||
|
||||
(in.Query != "user=testreader&pass=testpass¶m=value" &&
|
||||
in.Query != "user=testpublisher&pass=testpass¶m=value" &&
|
||||
in.Query != "param=value") {
|
||||
@@ -323,6 +325,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
Protocol: ProtocolRTSP,
|
||||
UserAgent: "testagent",
|
||||
Credentials: &Credentials{
|
||||
User: "testpublisher",
|
||||
Pass: "testpass",
|
||||
@@ -335,6 +338,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
Protocol: ProtocolRTSP,
|
||||
UserAgent: "testagent",
|
||||
Credentials: &Credentials{
|
||||
User: "invalid",
|
||||
Pass: "testpass",
|
||||
|
||||
@@ -26,6 +26,7 @@ type Request struct {
|
||||
Query string
|
||||
Protocol Protocol // only for ActionPublish, ActionRead
|
||||
ID *uuid.UUID // only for ActionPublish, ActionRead
|
||||
UserAgent string
|
||||
Credentials *Credentials
|
||||
IP net.IP
|
||||
CustomVerifyFunc func(expectedUser string, expectedPass string) bool
|
||||
|
||||
@@ -679,6 +679,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"rtcpPacketsSent": float64(0),
|
||||
"rtcpPacketsInError": float64(0),
|
||||
"conns": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["conns"],
|
||||
"userAgent": "gortsplib",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
@@ -740,6 +741,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"rtcpPacketsSent": float64(0),
|
||||
"rtcpPacketsInError": float64(0),
|
||||
"conns": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["conns"],
|
||||
"userAgent": "gortsplib",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
@@ -762,6 +764,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"user": "",
|
||||
"remoteAddr": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
|
||||
"state": "publish",
|
||||
"userAgent": "LNX 9,0,124,2",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
@@ -784,6 +787,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"user": "",
|
||||
"remoteAddr": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"],
|
||||
"state": "publish",
|
||||
"userAgent": "LNX 9,0,124,2",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
@@ -802,6 +806,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"user": "",
|
||||
"isCDN": false,
|
||||
"outboundBytes": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["outboundBytes"],
|
||||
"userAgent": "Go-http-client/1.1",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
@@ -855,6 +860,7 @@ func TestAPIProtocolListGet(t *testing.T) {
|
||||
"rtpPacketsLost": float64(0),
|
||||
"rtpPacketsReceived": float64(0),
|
||||
"rtpPacketsSent": float64(1),
|
||||
"userAgent": "Go-http-client/1.1",
|
||||
},
|
||||
},
|
||||
}, out1)
|
||||
|
||||
@@ -30,6 +30,7 @@ type APIHLSSession struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
User string `json:"user"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
IsCDN bool `json:"isCDN"`
|
||||
OutboundBytes uint64 `json:"outboundBytes"`
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type APIRTMPConn struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
User string `json:"user"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
InboundBytes uint64 `json:"inboundBytes"`
|
||||
OutboundBytes uint64 `json:"outboundBytes"`
|
||||
OutboundFramesDiscarded uint64 `json:"outboundFramesDiscarded"`
|
||||
|
||||
@@ -54,6 +54,7 @@ type APIRTSPSession struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
User string `json:"user"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
Transport *string `json:"transport"`
|
||||
Profile *string `json:"profile"`
|
||||
Conns []uuid.UUID `json:"conns"`
|
||||
|
||||
@@ -34,6 +34,7 @@ type APIWebRTCSession struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
User string `json:"user"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
InboundBytes uint64 `json:"inboundBytes"`
|
||||
InboundRTPPackets uint64 `json:"inboundRTPPackets"`
|
||||
InboundRTPPacketsLost uint64 `json:"inboundRTPPacketsLost"`
|
||||
|
||||
@@ -14,6 +14,7 @@ type PathAccessRequest struct {
|
||||
Query string
|
||||
Publish bool
|
||||
SkipAuth bool
|
||||
UserAgent string
|
||||
|
||||
// only if skipAuth = false
|
||||
Proto auth.Protocol
|
||||
@@ -36,6 +37,7 @@ func (r *PathAccessRequest) ToAuthRequest() *auth.Request {
|
||||
Query: r.Query,
|
||||
Protocol: r.Proto,
|
||||
ID: r.ID,
|
||||
UserAgent: r.UserAgent,
|
||||
Credentials: r.Credentials,
|
||||
IP: r.IP,
|
||||
CustomVerifyFunc: r.CustomVerifyFunc,
|
||||
|
||||
@@ -38,6 +38,7 @@ type session struct {
|
||||
created time.Time
|
||||
query string
|
||||
user string
|
||||
userAgent string
|
||||
lastRequestTime atomic.Int64
|
||||
bytesSent atomic.Uint64
|
||||
path defs.Path
|
||||
@@ -53,12 +54,14 @@ func (s *session) initialize(ctx *gin.Context) error {
|
||||
s.ip, _, _ = net.SplitHostPort(s.remoteAddr)
|
||||
s.created = time.Now()
|
||||
s.query = ctx.Request.URL.RawQuery
|
||||
s.userAgent = ctx.Request.UserAgent()
|
||||
s.lastRequestTime.Store(time.Now().UnixNano())
|
||||
|
||||
accessReq := defs.PathAccessRequest{
|
||||
Name: s.pathName,
|
||||
Query: s.query,
|
||||
Publish: false,
|
||||
UserAgent: s.userAgent,
|
||||
Proto: auth.ProtocolHLS,
|
||||
ID: &s.uuid,
|
||||
IP: net.ParseIP(ctx.ClientIP()),
|
||||
@@ -167,6 +170,7 @@ func (s *session) apiItem() *defs.APIHLSSession {
|
||||
Path: s.pathName,
|
||||
Query: s.query,
|
||||
User: s.user,
|
||||
UserAgent: s.userAgent,
|
||||
IsCDN: s.isCDN,
|
||||
OutboundBytes: outboundBytes,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type conn struct {
|
||||
pathName string
|
||||
query string
|
||||
user string
|
||||
userAgent string
|
||||
reader *stream.Reader
|
||||
}
|
||||
|
||||
@@ -141,6 +142,7 @@ func (c *conn) runReader() error {
|
||||
|
||||
c.mutex.Lock()
|
||||
c.rconn = conn
|
||||
c.userAgent = conn.FlashVer
|
||||
c.mutex.Unlock()
|
||||
|
||||
if !conn.Publish {
|
||||
@@ -158,6 +160,7 @@ func (c *conn) runRead() error {
|
||||
AccessRequest: defs.PathAccessRequest{
|
||||
Name: pathName,
|
||||
Query: c.rconn.URL.RawQuery,
|
||||
UserAgent: c.userAgent,
|
||||
Proto: auth.ProtocolRTMP,
|
||||
ID: &c.uuid,
|
||||
Credentials: &auth.Credentials{
|
||||
@@ -252,6 +255,7 @@ func (c *conn) runPublish() error {
|
||||
Name: pathName,
|
||||
Query: c.rconn.URL.RawQuery,
|
||||
Publish: true,
|
||||
UserAgent: c.userAgent,
|
||||
Proto: auth.ProtocolRTMP,
|
||||
ID: &c.uuid,
|
||||
Credentials: &auth.Credentials{
|
||||
@@ -344,6 +348,7 @@ func (c *conn) apiItem() *defs.APIRTMPConn {
|
||||
Path: c.pathName,
|
||||
Query: c.query,
|
||||
User: c.user,
|
||||
UserAgent: c.userAgent,
|
||||
InboundBytes: bytesReceived,
|
||||
OutboundBytes: bytesSent,
|
||||
BytesReceived: bytesReceived,
|
||||
|
||||
@@ -196,6 +196,7 @@ func TestServerPublish(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "user=myuser&pass=mypass¶m=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
OutboundBytes: list.Items[0].OutboundBytes,
|
||||
OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded,
|
||||
@@ -351,6 +352,7 @@ func TestServerRead(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "user=myuser&pass=mypass¶m=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
OutboundBytes: list.Items[0].OutboundBytes,
|
||||
OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded,
|
||||
|
||||
@@ -188,6 +188,7 @@ func TestServerPublish(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
InboundRTPPackets: list.Items[0].InboundRTPPackets,
|
||||
OutboundBytes: list.Items[0].OutboundBytes,
|
||||
@@ -510,6 +511,7 @@ func TestServerRead(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
InboundRTPPackets: list.Items[0].InboundRTPPackets,
|
||||
OutboundBytes: list.Items[0].OutboundBytes,
|
||||
|
||||
@@ -79,6 +79,7 @@ type session struct {
|
||||
outboundRTPPacketsDiscarded *counterdumper.Dumper
|
||||
mutex sync.RWMutex
|
||||
user string
|
||||
userAgent string
|
||||
mpegtsDemuxer *mpegtsDemuxer
|
||||
}
|
||||
|
||||
@@ -194,11 +195,17 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
|
||||
}
|
||||
}
|
||||
|
||||
var userAgent string
|
||||
if ua, ok := ctx.Request.Header["User-Agent"]; ok && len(ua) > 0 {
|
||||
userAgent = ua[0]
|
||||
}
|
||||
|
||||
res, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{
|
||||
AccessRequest: defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
Publish: true,
|
||||
UserAgent: userAgent,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
@@ -221,6 +228,7 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx)
|
||||
|
||||
s.mutex.Lock()
|
||||
s.user = res.User
|
||||
s.userAgent = userAgent
|
||||
s.mutex.Unlock()
|
||||
|
||||
return &base.Response{
|
||||
@@ -266,6 +274,11 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
|
||||
}
|
||||
}
|
||||
|
||||
var userAgent string
|
||||
if ua, ok := ctx.Request.Header["User-Agent"]; ok && len(ua) > 0 {
|
||||
userAgent = ua[0]
|
||||
}
|
||||
|
||||
switch s.rsession.State() {
|
||||
case gortsplib.ServerSessionStateInitial: // play
|
||||
res, err := s.pathManager.AddReader(defs.PathAddReaderReq{
|
||||
@@ -273,6 +286,7 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
|
||||
AccessRequest: defs.PathAccessRequest{
|
||||
Name: ctx.Path,
|
||||
Query: ctx.Query,
|
||||
UserAgent: userAgent,
|
||||
Proto: auth.ProtocolRTSP,
|
||||
ID: &c.uuid,
|
||||
Credentials: rtsp.Credentials(ctx.Request),
|
||||
@@ -304,6 +318,7 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx,
|
||||
|
||||
s.mutex.Lock()
|
||||
s.user = res.User
|
||||
s.userAgent = userAgent
|
||||
s.mutex.Unlock()
|
||||
|
||||
return &base.Response{
|
||||
@@ -389,6 +404,7 @@ func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Respons
|
||||
Query: s.rsession.Query(),
|
||||
Publish: true,
|
||||
SkipAuth: true,
|
||||
UserAgent: s.userAgent,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -509,6 +525,7 @@ func (s *session) apiItem() *defs.APIRTSPSession {
|
||||
}(),
|
||||
Query: s.rsession.Query(),
|
||||
User: s.user,
|
||||
UserAgent: s.userAgent,
|
||||
Transport: func() *string {
|
||||
transport := s.rsession.Transport()
|
||||
if transport == nil {
|
||||
|
||||
@@ -142,6 +142,7 @@ func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string,
|
||||
Name: pathName,
|
||||
Query: ctx.Request.URL.RawQuery,
|
||||
Publish: publish,
|
||||
UserAgent: ctx.Request.Header.Get("User-Agent"),
|
||||
Proto: auth.ProtocolWebRTC,
|
||||
Credentials: httpp.Credentials(ctx.Request),
|
||||
IP: net.ParseIP(ctx.ClientIP()),
|
||||
|
||||
@@ -469,6 +469,7 @@ func TestServerPublish(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
InboundRTPPackets: list.Items[0].InboundRTPPackets,
|
||||
InboundRTPPacketsLost: list.Items[0].InboundRTPPacketsLost,
|
||||
@@ -763,6 +764,7 @@ func TestServerRead(t *testing.T) {
|
||||
Path: "teststream",
|
||||
Query: "param=value",
|
||||
User: "myuser",
|
||||
UserAgent: list.Items[0].UserAgent,
|
||||
InboundBytes: list.Items[0].InboundBytes,
|
||||
InboundRTPPackets: list.Items[0].InboundRTPPackets,
|
||||
InboundRTPPacketsLost: list.Items[0].InboundRTPPacketsLost,
|
||||
|
||||
@@ -341,6 +341,7 @@ func (s *session) runPublish(req *initialRequestReq) (int, error) {
|
||||
Name: s.pathName,
|
||||
Query: s.httpRequest.URL.RawQuery,
|
||||
Publish: true,
|
||||
UserAgent: s.httpRequest.Header.Get("User-Agent"),
|
||||
Proto: auth.ProtocolWebRTC,
|
||||
ID: &s.uuid,
|
||||
Credentials: httpp.Credentials(s.httpRequest),
|
||||
@@ -453,6 +454,7 @@ func (s *session) runPublish(req *initialRequestReq) (int, error) {
|
||||
Query: s.httpRequest.URL.RawQuery,
|
||||
Publish: true,
|
||||
SkipAuth: true,
|
||||
UserAgent: s.httpRequest.Header.Get("User-Agent"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -482,6 +484,7 @@ func (s *session) runRead(req *initialRequestReq) (int, error) {
|
||||
AccessRequest: defs.PathAccessRequest{
|
||||
Name: s.pathName,
|
||||
Query: s.httpRequest.URL.RawQuery,
|
||||
UserAgent: s.httpRequest.Header.Get("User-Agent"),
|
||||
Proto: auth.ProtocolWebRTC,
|
||||
ID: &s.uuid,
|
||||
Credentials: httpp.Credentials(s.httpRequest),
|
||||
@@ -754,6 +757,7 @@ func (s *session) apiItem() *defs.APIWebRTCSession {
|
||||
Path: s.pathName,
|
||||
Query: s.httpRequest.URL.RawQuery,
|
||||
User: s.user,
|
||||
UserAgent: s.httpRequest.Header.Get("User-Agent"),
|
||||
InboundBytes: bytesReceived,
|
||||
InboundRTPPackets: rtpPacketsReceived,
|
||||
InboundRTPPacketsLost: rtpPacketsLost,
|
||||
|
||||
+1
-12
@@ -94,18 +94,7 @@ authInternalUsers:
|
||||
# HTTP-based authentication.
|
||||
# URL called to perform authentication. Every time a user wants
|
||||
# to authenticate, the server calls this URL with the POST method
|
||||
# and a body containing:
|
||||
# {
|
||||
# "user": "user",
|
||||
# "password": "password",
|
||||
# "token": "token",
|
||||
# "ip": "ip",
|
||||
# "action": "publish|read|playback|api|metrics|pprof",
|
||||
# "path": "path",
|
||||
# "protocol": "rtsp|rtmp|hls|webrtc|srt",
|
||||
# "id": "id",
|
||||
# "query": "query"
|
||||
# }
|
||||
# and a payload described in the documentation.
|
||||
# If the response code is 20x, authentication is accepted, otherwise
|
||||
# it is discarded.
|
||||
authHTTPAddress:
|
||||
|
||||
Reference in New Issue
Block a user