mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 07:06:58 +08:00
add ServerConn.VerifyCredentials() (#555)
This commit is contained in:
@@ -30,6 +30,7 @@ Features:
|
||||
* Pause without disconnecting from the server
|
||||
* Server
|
||||
* Handle requests from clients
|
||||
* Validate client credentials
|
||||
* Read media streams from clients ("record")
|
||||
* Read streams with the UDP or TCP transport protocol
|
||||
* Read TLS-encrypted streams (TCP only)
|
||||
@@ -94,6 +95,7 @@ Features:
|
||||
* [client-record-format-vp9](examples/client-record-format-vp9/main.go)
|
||||
* [server](examples/server/main.go)
|
||||
* [server-tls](examples/server-tls/main.go)
|
||||
* [server-auth](examples/server-auth/main.go)
|
||||
* [server-h264-save-to-disk](examples/server-h264-save-to-disk/main.go)
|
||||
* [proxy](examples/proxy/main.go)
|
||||
|
||||
|
@@ -34,6 +34,10 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
|
||||
)
|
||||
|
||||
const (
|
||||
clientUserAgent = "gortsplib"
|
||||
)
|
||||
|
||||
// avoid an int64 overflow and preserve resolution by splitting division into two parts:
|
||||
// first add the integer part, then the decimal part.
|
||||
func multiplyAndDivide(v, m, d time.Duration) time.Duration {
|
||||
@@ -386,7 +390,7 @@ func (c *Client) Start(scheme string, host string) error {
|
||||
return fmt.Errorf("MaxPacketSize must be less than %d", udpMaxPayloadSize)
|
||||
}
|
||||
if c.UserAgent == "" {
|
||||
c.UserAgent = "gortsplib"
|
||||
c.UserAgent = clientUserAgent
|
||||
}
|
||||
|
||||
// system functions
|
||||
|
195
examples/server-auth/main.go
Normal file
195
examples/server-auth/main.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
|
||||
)
|
||||
|
||||
// This example shows how to
|
||||
// 1. create a RTSP server which accepts plain connections
|
||||
// 2. allow a single client to publish a stream with TCP or UDP, if it provides credentials
|
||||
// 3. allow multiple clients to read that stream with TCP, UDP or UDP-multicast, if they provide credentials
|
||||
|
||||
const (
|
||||
// credentials required to publish the stream
|
||||
publishUser = "publishuser"
|
||||
publishPass = "publishpass"
|
||||
|
||||
// credentials required to read the stream
|
||||
readUser = "readuser"
|
||||
readPass = "readpass"
|
||||
)
|
||||
|
||||
type serverHandler struct {
|
||||
s *gortsplib.Server
|
||||
mutex sync.Mutex
|
||||
stream *gortsplib.ServerStream
|
||||
publisher *gortsplib.ServerSession
|
||||
}
|
||||
|
||||
// called when a connection is opened.
|
||||
func (sh *serverHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
|
||||
log.Printf("conn opened")
|
||||
}
|
||||
|
||||
// called when a connection is closed.
|
||||
func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
|
||||
log.Printf("conn closed (%v)", ctx.Error)
|
||||
}
|
||||
|
||||
// called when a session is opened.
|
||||
func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
|
||||
log.Printf("session opened")
|
||||
}
|
||||
|
||||
// called when a session is closed.
|
||||
func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
|
||||
log.Printf("session closed")
|
||||
|
||||
sh.mutex.Lock()
|
||||
defer sh.mutex.Unlock()
|
||||
|
||||
// if the session is the publisher,
|
||||
// close the stream and disconnect any reader.
|
||||
if sh.stream != nil && ctx.Session == sh.publisher {
|
||||
sh.stream.Close()
|
||||
sh.stream = nil
|
||||
}
|
||||
}
|
||||
|
||||
// called when receiving a DESCRIBE request.
|
||||
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
log.Printf("describe request")
|
||||
|
||||
// Verify reader credentials.
|
||||
// In case of readers, credentials have to be verified during DESCRIBE and SETUP.
|
||||
ok := ctx.Conn.VerifyCredentials(ctx.Request, readUser, readPass)
|
||||
if !ok {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
}, nil, liberrors.ErrServerAuth{}
|
||||
}
|
||||
|
||||
sh.mutex.Lock()
|
||||
defer sh.mutex.Unlock()
|
||||
|
||||
// no one is publishing yet
|
||||
if sh.stream == nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusNotFound,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// send medias that are being published to the client
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, sh.stream, nil
|
||||
}
|
||||
|
||||
// called when receiving an ANNOUNCE request.
|
||||
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||
log.Printf("announce request")
|
||||
|
||||
// Verify publisher credentials.
|
||||
// In case of publishers, credentials have to be verified during ANNOUNCE.
|
||||
ok := ctx.Conn.VerifyCredentials(ctx.Request, publishUser, publishPass)
|
||||
if !ok {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
}, liberrors.ErrServerAuth{}
|
||||
}
|
||||
|
||||
sh.mutex.Lock()
|
||||
defer sh.mutex.Unlock()
|
||||
|
||||
// disconnect existing publisher
|
||||
if sh.stream != nil {
|
||||
sh.stream.Close()
|
||||
sh.publisher.Close()
|
||||
}
|
||||
|
||||
// create the stream and save the publisher
|
||||
sh.stream = gortsplib.NewServerStream(sh.s, ctx.Description)
|
||||
sh.publisher = ctx.Session
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// called when receiving a SETUP request.
|
||||
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
log.Printf("setup request")
|
||||
|
||||
// Verify reader credentials.
|
||||
// In case of readers, credentials have to be verified during DESCRIBE and SETUP.
|
||||
if ctx.Session.State() == gortsplib.ServerSessionStateInitial {
|
||||
ok := ctx.Conn.VerifyCredentials(ctx.Request, readUser, readPass)
|
||||
if !ok {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
}, nil, liberrors.ErrServerAuth{}
|
||||
}
|
||||
}
|
||||
|
||||
// no one is publishing yet
|
||||
if sh.stream == nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusNotFound,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, sh.stream, nil
|
||||
}
|
||||
|
||||
// called when receiving a PLAY request.
|
||||
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||
log.Printf("play request")
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// called when receiving a RECORD request.
|
||||
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||
log.Printf("record request")
|
||||
|
||||
// called when receiving a RTP packet
|
||||
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
||||
// route the RTP packet to all readers
|
||||
sh.stream.WritePacketRTP(medi, pkt)
|
||||
})
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// configure the server
|
||||
h := &serverHandler{}
|
||||
h.s = &gortsplib.Server{
|
||||
Handler: h,
|
||||
RTSPAddress: ":8554",
|
||||
UDPRTPAddress: ":8000",
|
||||
UDPRTCPAddress: ":8001",
|
||||
MulticastIPRange: "224.1.0.0/16",
|
||||
MulticastRTPPort: 8002,
|
||||
MulticastRTCPPort: 8003,
|
||||
}
|
||||
|
||||
// start server and wait until a fatal error
|
||||
log.Printf("server is ready")
|
||||
panic(h.s.StartAndWait())
|
||||
}
|
@@ -53,11 +53,11 @@ func (se *Sender) AddAuthorization(req *base.Request) {
|
||||
Method: se.authHeader.Method,
|
||||
}
|
||||
|
||||
h.Username = se.user
|
||||
|
||||
if se.authHeader.Method == headers.AuthMethodBasic {
|
||||
h.BasicUser = se.user
|
||||
h.BasicPass = se.pass
|
||||
} else { // digest
|
||||
h.Username = se.user
|
||||
h.Realm = se.authHeader.Realm
|
||||
h.Nonce = se.authHeader.Nonce
|
||||
h.URI = urStr
|
||||
|
@@ -61,7 +61,7 @@ const (
|
||||
VerifyMethodDigestSHA256
|
||||
)
|
||||
|
||||
// Verify validates a request sent by a client.
|
||||
// Verify verifies a request sent by a client.
|
||||
func Verify(
|
||||
req *base.Request,
|
||||
user string,
|
||||
@@ -119,7 +119,7 @@ func Verify(
|
||||
}
|
||||
|
||||
case auth.Method == headers.AuthMethodBasic && contains(methods, VerifyMethodBasic):
|
||||
if auth.BasicUser != user {
|
||||
if auth.Username != user {
|
||||
return fmt.Errorf("authentication failed")
|
||||
}
|
||||
|
||||
|
@@ -13,11 +13,16 @@ type Authorization struct {
|
||||
// authentication method
|
||||
Method AuthMethod
|
||||
|
||||
// username
|
||||
Username string
|
||||
|
||||
//
|
||||
// Basic authentication fields
|
||||
//
|
||||
|
||||
// user
|
||||
//
|
||||
// Deprecated: replaced by Username.
|
||||
BasicUser string
|
||||
|
||||
// password
|
||||
@@ -27,9 +32,6 @@ type Authorization struct {
|
||||
// Digest authentication fields
|
||||
//
|
||||
|
||||
// username
|
||||
Username string
|
||||
|
||||
// realm
|
||||
Realm string
|
||||
|
||||
@@ -89,7 +91,8 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
return fmt.Errorf("invalid value")
|
||||
}
|
||||
|
||||
h.BasicUser, h.BasicPass = tmp2[0], tmp2[1]
|
||||
h.Username, h.BasicPass = tmp2[0], tmp2[1]
|
||||
h.BasicUser = h.Username
|
||||
} else { // digest
|
||||
kvs, err := keyValParse(v0, ',')
|
||||
if err != nil {
|
||||
@@ -149,8 +152,11 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
// Marshal encodes an Authorization header.
|
||||
func (h Authorization) Marshal() base.HeaderValue {
|
||||
if h.Method == AuthMethodBasic {
|
||||
if h.BasicUser != "" {
|
||||
h.Username = h.BasicUser
|
||||
}
|
||||
return base.HeaderValue{"Basic " +
|
||||
base64.StdEncoding.EncodeToString([]byte(h.BasicUser+":"+h.BasicPass))}
|
||||
base64.StdEncoding.EncodeToString([]byte(h.Username+":"+h.BasicPass))}
|
||||
}
|
||||
|
||||
ret := "Digest " +
|
||||
|
@@ -24,6 +24,7 @@ var casesAuthorization = []struct {
|
||||
base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
|
||||
Authorization{
|
||||
Method: AuthMethodBasic,
|
||||
Username: "myuser",
|
||||
BasicUser: "myuser",
|
||||
BasicPass: "mypass",
|
||||
},
|
||||
|
@@ -270,3 +270,13 @@ func (ErrServerInvalidSetupPath) Error() string {
|
||||
"This typically happens when VLC fails a request, and then switches to an " +
|
||||
"unsupported RTSP dialect"
|
||||
}
|
||||
|
||||
// ErrServerAuth is an error that can be returned by a server.
|
||||
// If a client did not provide credentials, it will be asked for
|
||||
// credentials instead of being kicked out.
|
||||
type ErrServerAuth struct{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e ErrServerAuth) Error() string {
|
||||
return "authentication error"
|
||||
}
|
||||
|
14
server.go
14
server.go
@@ -9,10 +9,16 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
|
||||
)
|
||||
|
||||
const (
|
||||
serverHeader = "gortsplib"
|
||||
serverAuthRealm = "ipcam"
|
||||
)
|
||||
|
||||
func extractPort(address string) (int, error) {
|
||||
_, tmp, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
@@ -88,6 +94,9 @@ type Server struct {
|
||||
MaxPacketSize int
|
||||
// disable automatic RTCP sender reports.
|
||||
DisableRTCPSenderReports bool
|
||||
// authentication methods.
|
||||
// It defaults to plain and digest+MD5.
|
||||
AuthMethods []auth.VerifyMethod
|
||||
|
||||
//
|
||||
// handler (optional)
|
||||
@@ -156,6 +165,11 @@ func (s *Server) Start() error {
|
||||
} else if s.MaxPacketSize > udpMaxPayloadSize {
|
||||
return fmt.Errorf("MaxPacketSize must be less than %d", udpMaxPayloadSize)
|
||||
}
|
||||
if len(s.AuthMethods) == 0 {
|
||||
// disable VerifyMethodDigestSHA256 unless explicitly set
|
||||
// since it prevents FFmpeg from authenticating
|
||||
s.AuthMethods = []auth.VerifyMethod{auth.VerifyMethodBasic, auth.VerifyMethodDigestMD5}
|
||||
}
|
||||
|
||||
// system functions
|
||||
if s.Listen == nil {
|
||||
|
@@ -10,10 +10,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/bytecounter"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/conn"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
|
||||
)
|
||||
|
||||
@@ -46,6 +48,12 @@ func serverSideDescription(d *description.Session) *description.Session {
|
||||
return out
|
||||
}
|
||||
|
||||
func credentialsProvided(req *base.Request) bool {
|
||||
var auth headers.Authorization
|
||||
err := auth.Unmarshal(req.Header["Authorization"])
|
||||
return err == nil && auth.Username != ""
|
||||
}
|
||||
|
||||
type readReq struct {
|
||||
req *base.Request
|
||||
res chan error
|
||||
@@ -64,6 +72,7 @@ type ServerConn struct {
|
||||
conn *conn.Conn
|
||||
session *ServerSession
|
||||
reader *serverConnReader
|
||||
authNonce string
|
||||
|
||||
// in
|
||||
chRemoveSession chan *ServerSession
|
||||
@@ -137,6 +146,48 @@ func (sc *ServerConn) Stats() *StatsConn {
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCredentials verifies credentials provided by the user.
|
||||
func (sc *ServerConn) VerifyCredentials(
|
||||
req *base.Request,
|
||||
expectedUser string,
|
||||
expectedPass string,
|
||||
) bool {
|
||||
// we do not support using an empty string as user
|
||||
// since it interferes with credentialsProvided()
|
||||
if expectedUser == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if sc.authNonce == "" {
|
||||
n, err := auth.GenerateNonce()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
sc.authNonce = n
|
||||
}
|
||||
|
||||
err := auth.Verify(
|
||||
req,
|
||||
expectedUser,
|
||||
expectedPass,
|
||||
sc.s.AuthMethods,
|
||||
serverAuthRealm,
|
||||
sc.authNonce)
|
||||
|
||||
return (err == nil)
|
||||
}
|
||||
|
||||
func (sc *ServerConn) handleAuthError(req *base.Request, res *base.Response) error {
|
||||
// if credentials have not been provided, clear error and send the WWW-Authenticate header.
|
||||
if !credentialsProvided(req) {
|
||||
res.Header["WWW-Authenticate"] = auth.GenerateWWWAuthenticate(sc.s.AuthMethods, serverAuthRealm, sc.authNonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
// if credentials have been provided (and are wrong), close the connection.
|
||||
return liberrors.ErrServerAuth{}
|
||||
}
|
||||
|
||||
func (sc *ServerConn) ip() net.IP {
|
||||
return sc.remoteAddr.IP
|
||||
}
|
||||
@@ -386,14 +437,20 @@ func (sc *ServerConn) handleRequestOuter(req *base.Request) error {
|
||||
res.Header = make(base.Header)
|
||||
}
|
||||
|
||||
// handle auth errors
|
||||
var eerr1 liberrors.ErrServerAuth
|
||||
if errors.As(err, &eerr1) {
|
||||
err = sc.handleAuthError(req, res)
|
||||
}
|
||||
|
||||
// add cseq
|
||||
var eerr liberrors.ErrServerCSeqMissing
|
||||
if !errors.As(err, &eerr) {
|
||||
var eerr2 liberrors.ErrServerCSeqMissing
|
||||
if !errors.As(err, &eerr2) {
|
||||
res.Header["CSeq"] = req.Header["CSeq"]
|
||||
}
|
||||
|
||||
// add server
|
||||
res.Header["Server"] = base.HeaderValue{"gortsplib"}
|
||||
res.Header["Server"] = base.HeaderValue{serverHeader}
|
||||
|
||||
if h, ok := sc.s.Handler.(ServerHandlerOnResponse); ok {
|
||||
h.OnResponse(sc, res)
|
||||
|
@@ -3,6 +3,7 @@ package gortsplib
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/conn"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
|
||||
)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
@@ -1035,20 +1037,87 @@ func TestServerSessionTeardown(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServerAuth(t *testing.T) {
|
||||
nonce, err := auth.GenerateNonce()
|
||||
require.NoError(t, err)
|
||||
for _, method := range []string{"all", "basic", "digest_md5", "digest_sha256"} {
|
||||
t.Run(method, func(t *testing.T) {
|
||||
s := &Server{
|
||||
Handler: &testServerHandler{
|
||||
onAnnounce: func(ctx *ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||
ok := ctx.Conn.VerifyCredentials(ctx.Request, "myuser", "mypass")
|
||||
if !ok {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
}, liberrors.ErrServerAuth{}
|
||||
}
|
||||
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
RTSPAddress: "localhost:8554",
|
||||
AuthMethods: func() []auth.VerifyMethod {
|
||||
switch method {
|
||||
case "basic":
|
||||
return []auth.VerifyMethod{auth.VerifyMethodBasic}
|
||||
|
||||
case "digest_md5":
|
||||
return []auth.VerifyMethod{auth.VerifyMethodDigestMD5}
|
||||
|
||||
case "digest_sha256":
|
||||
return []auth.VerifyMethod{auth.VerifyMethodDigestSHA256}
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
}
|
||||
|
||||
err := s.Start()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
nconn, err := net.Dial("tcp", "localhost:8554")
|
||||
require.NoError(t, err)
|
||||
defer nconn.Close()
|
||||
conn := conn.NewConn(nconn)
|
||||
|
||||
medias := []*description.Media{testH264Media}
|
||||
|
||||
req := base.Request{
|
||||
Method: base.Announce,
|
||||
URL: mustParseURL("rtsp://localhost:8554/teststream"),
|
||||
Header: base.Header{
|
||||
"CSeq": base.HeaderValue{"1"},
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
},
|
||||
Body: mediasToSDP(medias),
|
||||
}
|
||||
|
||||
res, err := writeReqReadRes(conn, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.StatusUnauthorized, res.StatusCode)
|
||||
|
||||
sender, err := auth.NewSender(res.Header["WWW-Authenticate"], "myuser", "mypass")
|
||||
require.NoError(t, err)
|
||||
|
||||
sender.AddAuthorization(&req)
|
||||
res, err = writeReqReadRes(conn, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.StatusOK, res.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerAuthFail(t *testing.T) {
|
||||
s := &Server{
|
||||
Handler: &testServerHandler{
|
||||
onConnClose: func(ctx *ServerHandlerOnConnCloseCtx) {
|
||||
require.EqualError(t, ctx.Error, "authentication error")
|
||||
},
|
||||
onAnnounce: func(ctx *ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||
err2 := auth.Verify(ctx.Request, "myuser", "mypass", nil, "IPCAM", nonce)
|
||||
if err2 != nil {
|
||||
return &base.Response{ //nolint:nilerr
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
Header: base.Header{
|
||||
"WWW-Authenticate": auth.GenerateWWWAuthenticate(nil, "IPCAM", nonce),
|
||||
},
|
||||
}, nil
|
||||
ok := ctx.Conn.VerifyCredentials(ctx.Request, "myuser2", "mypass2")
|
||||
if !ok {
|
||||
return &base.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
}, liberrors.ErrServerAuth{}
|
||||
}
|
||||
|
||||
return &base.Response{
|
||||
@@ -1059,7 +1128,7 @@ func TestServerAuth(t *testing.T) {
|
||||
RTSPAddress: "localhost:8554",
|
||||
}
|
||||
|
||||
err = s.Start()
|
||||
err := s.Start()
|
||||
require.NoError(t, err)
|
||||
defer s.Close()
|
||||
|
||||
@@ -1088,7 +1157,11 @@ func TestServerAuth(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
sender.AddAuthorization(&req)
|
||||
|
||||
res, err = writeReqReadRes(conn, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.StatusOK, res.StatusCode)
|
||||
require.Equal(t, base.StatusUnauthorized, res.StatusCode)
|
||||
|
||||
_, err = writeReqReadRes(conn, req)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user