Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split headers.Authenticate and headers.Authorization #523

Merged
merged 1 commit into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions client_play_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1691,12 +1691,13 @@ func TestClientPlayRedirect(t *testing.T) {
authOpaque := "exampleOpaque"
authStale := "FALSE"
authAlg := "MD5"

err = conn.WriteResponse(&base.Response{
Header: base.Header{
"WWW-Authenticate": headers.Authenticate{
Method: headers.AuthDigest,
Realm: &authRealm,
Nonce: &authNonce,
Realm: authRealm,
Nonce: authNonce,
Opaque: &authOpaque,
Stale: &authStale,
Algorithm: &authAlg,
Expand All @@ -1706,13 +1707,16 @@ func TestClientPlayRedirect(t *testing.T) {
})
require.NoError(t, err)
}

req, err = conn.ReadRequest()
require.NoError(t, err)

authHeaderVal, exists := req.Header["Authorization"]
require.True(t, exists)
var authHeader headers.Authenticate

var authHeader headers.Authorization
require.NoError(t, authHeader.Unmarshal(authHeaderVal))
require.Equal(t, *authHeader.Username, "testusr")
require.Equal(t, authHeader.Username, "testusr")
require.Equal(t, base.Describe, req.Method)
}

Expand Down
60 changes: 18 additions & 42 deletions pkg/auth/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@ func findHeader(v base.HeaderValue, prefix string) string {

// Sender allows to send credentials.
type Sender struct {
user string
pass string
method headers.AuthMethod
realm string
nonce string
user string
pass string
authenticateHeader *headers.Authenticate
}

// NewSender allocates a Sender.
Expand All @@ -38,20 +36,10 @@ func NewSender(v base.HeaderValue, user string, pass string) (*Sender, error) {
return nil, err
}

if auth.Realm == nil {
return nil, fmt.Errorf("realm is missing")
}

if auth.Nonce == nil {
return nil, fmt.Errorf("nonce is missing")
}

return &Sender{
user: user,
pass: pass,
method: headers.AuthDigest,
realm: *auth.Realm,
nonce: *auth.Nonce,
user: user,
pass: pass,
authenticateHeader: &auth,
}, nil
}

Expand All @@ -62,15 +50,10 @@ func NewSender(v base.HeaderValue, user string, pass string) (*Sender, error) {
return nil, err
}

if auth.Realm == nil {
return nil, fmt.Errorf("realm is missing")
}

return &Sender{
user: user,
pass: pass,
method: headers.AuthBasic,
realm: *auth.Realm,
user: user,
pass: pass,
authenticateHeader: &auth,
}, nil
}

Expand All @@ -82,26 +65,19 @@ func (se *Sender) AddAuthorization(req *base.Request) {
urStr := req.URL.CloneWithoutCredentials().String()

h := headers.Authorization{
Method: se.method,
Method: se.authenticateHeader.Method,
}

switch se.method {
case headers.AuthBasic:
if se.authenticateHeader.Method == headers.AuthBasic {
h.BasicUser = se.user
h.BasicPass = se.pass

default: // headers.AuthDigest
response := md5Hex(md5Hex(se.user+":"+se.realm+":"+se.pass) + ":" +
se.nonce + ":" + md5Hex(string(req.Method)+":"+urStr))

h.DigestValues = headers.Authenticate{
Method: headers.AuthDigest,
Username: &se.user,
Realm: &se.realm,
Nonce: &se.nonce,
URI: &urStr,
Response: &response,
}
} else { // digest
h.Username = se.user
h.Realm = se.authenticateHeader.Realm
h.Nonce = se.authenticateHeader.Nonce
h.URI = urStr
h.Response = md5Hex(md5Hex(se.user+":"+se.authenticateHeader.Realm+":"+se.pass) + ":" +
se.authenticateHeader.Nonce + ":" + md5Hex(string(req.Method)+":"+urStr))
}

if req.Header == nil {
Expand Down
55 changes: 11 additions & 44 deletions pkg/auth/sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,18 @@ package auth
import (
"testing"

"github.com/stretchr/testify/require"

"github.com/bluenviron/gortsplib/v4/pkg/base"
)

func TestSenderErrors(t *testing.T) {
for _, ca := range []struct {
name string
hv base.HeaderValue
err string
}{
{
"invalid method",
base.HeaderValue{`Invalid`},
"no authentication methods available",
},
{
"digest invalid",
base.HeaderValue{`Digest`},
"unable to split between method and keys (Digest)",
},
{
"digest, missing realm",
base.HeaderValue{`Digest nonce=123`},
"realm is missing",
},
{
"digest, missing nonce",
base.HeaderValue{`Digest realm=123`},
"nonce is missing",
},
{
"basic invalid",
base.HeaderValue{`Basic`},
"unable to split between method and keys (Basic)",
},
{
"basic, missing realm",
base.HeaderValue{`Basic nonce=123`},
"realm is missing",
},
} {
t.Run(ca.name, func(t *testing.T) {
_, err := NewSender(ca.hv, "myuser", "mypass")
require.EqualError(t, err, ca.err)
})
}
func FuzzSender(f *testing.F) {
f.Add(`Invalid`)
f.Add(`Digest`)
f.Add(`Digest nonce=123`)
f.Add(`Digest realm=123`)
f.Add(`Basic`)
f.Add(`Basic nonce=123`)

f.Fuzz(func(t *testing.T, a string) {
NewSender(base.HeaderValue{a}, "myuser", "mypass") //nolint:errcheck
})
}
38 changes: 9 additions & 29 deletions pkg/auth/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ func GenerateWWWAuthenticate(methods []headers.AuthMethod, realm string, nonce s
case headers.AuthBasic:
ret = append(ret, (&headers.Authenticate{
Method: headers.AuthBasic,
Realm: &realm,
Realm: realm,
}).Marshal()...)

case headers.AuthDigest:
ret = append(ret, headers.Authenticate{
Method: headers.AuthDigest,
Realm: &realm,
Nonce: &nonce,
Realm: realm,
Nonce: nonce,
}.Marshal()...)
}
}
Expand Down Expand Up @@ -92,46 +92,26 @@ func Validate(
return fmt.Errorf("authentication failed")
}
case auth.Method == headers.AuthDigest && contains(methods, headers.AuthDigest):
if auth.DigestValues.Realm == nil {
return fmt.Errorf("realm is missing")
}

if auth.DigestValues.Nonce == nil {
return fmt.Errorf("nonce is missing")
}

if auth.DigestValues.Username == nil {
return fmt.Errorf("username is missing")
}

if auth.DigestValues.URI == nil {
return fmt.Errorf("uri is missing")
}

if auth.DigestValues.Response == nil {
return fmt.Errorf("response is missing")
}

if *auth.DigestValues.Nonce != nonce {
if auth.Nonce != nonce {
return fmt.Errorf("wrong nonce")
}

if *auth.DigestValues.Realm != realm {
if auth.Realm != realm {
return fmt.Errorf("wrong realm")
}

if *auth.DigestValues.Username != user {
if auth.Username != user {
return fmt.Errorf("authentication failed")
}

ur := req.URL

if *auth.DigestValues.URI != ur.String() {
if auth.URI != ur.String() {
// in SETUP requests, VLC strips the control attribute.
// try again with the base URL.
if baseURL != nil {
ur = baseURL
if *auth.DigestValues.URI != ur.String() {
if auth.URI != ur.String() {
return fmt.Errorf("wrong URL")
}
} else {
Expand All @@ -142,7 +122,7 @@ func Validate(
response := md5Hex(md5Hex(user+":"+realm+":"+pass) +
":" + nonce + ":" + md5Hex(string(req.Method)+":"+ur.String()))

if *auth.DigestValues.Response != response {
if auth.Response != response {
return fmt.Errorf("authentication failed")
}
default:
Expand Down
114 changes: 47 additions & 67 deletions pkg/auth/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,55 @@ package auth
import (
"testing"

"github.com/stretchr/testify/require"

"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/stretchr/testify/require"
)

func TestValidateErrors(t *testing.T) {
for _, ca := range []struct {
name string
hv base.HeaderValue
err string
}{
{
"invalid auth",
base.HeaderValue{`Invalid`},
"invalid authorization header",
},
{
"digest missing realm",
base.HeaderValue{`Digest `},
"realm is missing",
},
{
"digest missing nonce",
base.HeaderValue{`Digest realm=123`},
"nonce is missing",
},
{
"digest missing username",
base.HeaderValue{`Digest realm=123,nonce=123`},
"username is missing",
},
{
"digest missing uri",
base.HeaderValue{`Digest realm=123,nonce=123,username=123`},
"uri is missing",
},
{
"digest missing response",
base.HeaderValue{`Digest realm=123,nonce=123,username=123,uri=123`},
"response is missing",
},
{
"digest wrong nonce",
base.HeaderValue{`Digest realm=123,nonce=123,username=123,uri=123,response=123`},
"wrong nonce",
},
{
"digest wrong realm",
base.HeaderValue{`Digest realm=123,nonce=abcde,username=123,uri=123,response=123`},
"wrong realm",
},
} {
t.Run(ca.name, func(t *testing.T) {
err := Validate(
&base.Request{
Method: base.Describe,
URL: nil,
Header: base.Header{
"Authorization": ca.hv,
},
func FuzzValidate(f *testing.F) {
f.Add(`Invalid`)
f.Add(`Digest `)
f.Add(`Digest realm=123`)
f.Add(`Digest realm=123,nonce=123`)
f.Add(`Digest realm=123,nonce=123,username=123`)
f.Add(`Digest realm=123,nonce=123,username=123,uri=123`)
f.Add(`Digest realm=123,nonce=123,username=123,uri=123,response=123`)
f.Add(`Digest realm=123,nonce=abcde,username=123,uri=123,response=123`)

f.Fuzz(func(t *testing.T, a string) {
Validate( //nolint:errcheck
&base.Request{
Method: base.Describe,
URL: nil,
Header: base.Header{
"Authorization": base.HeaderValue{a},
},
"myuser",
"mypass",
nil,
nil,
"IPCAM",
"abcde",
)
require.EqualError(t, err, ca.err)
})
}
},
"myuser",
"mypass",
nil,
nil,
"IPCAM",
"abcde",
)
})
}

func TestValidateAdditionalErrors(t *testing.T) {
err := Validate(
&base.Request{
Method: base.Describe,
URL: nil,
Header: base.Header{
"Authorization": base.HeaderValue{"Basic bXl1c2VyOm15cGFzcw=="},
},
},
"myuser",
"mypass",
nil,
[]headers.AuthMethod{headers.AuthDigest},
"IPCAM",
"abcde",
)
require.Error(t, err)
}
Loading
Loading