mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
split headers.Authenticate and headers.Authorization (#523)
This commit is contained in:
@@ -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,
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -21,9 +21,7 @@ func findHeader(v base.HeaderValue, prefix string) string {
|
||||
type Sender struct {
|
||||
user string
|
||||
pass string
|
||||
method headers.AuthMethod
|
||||
realm string
|
||||
nonce string
|
||||
authenticateHeader *headers.Authenticate
|
||||
}
|
||||
|
||||
// NewSender allocates a Sender.
|
||||
@@ -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,
|
||||
authenticateHeader: &auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
authenticateHeader: &auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -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()...)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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:
|
||||
|
@@ -3,65 +3,28 @@ 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(
|
||||
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": ca.hv,
|
||||
"Authorization": base.HeaderValue{a},
|
||||
},
|
||||
},
|
||||
"myuser",
|
||||
@@ -71,7 +34,24 @@ func TestValidateErrors(t *testing.T) {
|
||||
"IPCAM",
|
||||
"abcde",
|
||||
)
|
||||
require.EqualError(t, err, ca.err)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@@ -19,37 +19,32 @@ const (
|
||||
AuthDigest
|
||||
)
|
||||
|
||||
// Authenticate is an Authenticate or a WWW-Authenticate header.
|
||||
// Authenticate is a WWW-Authenticate header.
|
||||
type Authenticate struct {
|
||||
// authentication method
|
||||
Method AuthMethod
|
||||
|
||||
// (optional) username
|
||||
Username *string
|
||||
// realm
|
||||
Realm string
|
||||
|
||||
// (optional) realm
|
||||
Realm *string
|
||||
//
|
||||
// Digest authentication fields
|
||||
//
|
||||
|
||||
// (optional) nonce
|
||||
Nonce *string
|
||||
// nonce
|
||||
Nonce string
|
||||
|
||||
// (optional) uri
|
||||
URI *string
|
||||
|
||||
// (optional) response
|
||||
Response *string
|
||||
|
||||
// (optional) opaque
|
||||
// opaque
|
||||
Opaque *string
|
||||
|
||||
// (optional) stale
|
||||
// stale
|
||||
Stale *string
|
||||
|
||||
// (optional) algorithm
|
||||
// algorithm
|
||||
Algorithm *string
|
||||
}
|
||||
|
||||
// Unmarshal decodes an Authenticate or a WWW-Authenticate header.
|
||||
// Unmarshal decodes a WWW-Authenticate header.
|
||||
func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
if len(v) == 0 {
|
||||
return fmt.Errorf("value not provided")
|
||||
@@ -78,29 +73,46 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
return fmt.Errorf("invalid method (%s)", method)
|
||||
}
|
||||
|
||||
if h.Method == AuthBasic {
|
||||
kvs, err := keyValParse(v0, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
realmReceived := false
|
||||
|
||||
for k, rv := range kvs {
|
||||
v := rv
|
||||
|
||||
if k == "realm" {
|
||||
h.Realm = v
|
||||
realmReceived = true
|
||||
}
|
||||
}
|
||||
|
||||
if !realmReceived {
|
||||
return fmt.Errorf("realm is missing")
|
||||
}
|
||||
} else { // digest
|
||||
kvs, err := keyValParse(v0, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
realmReceived := false
|
||||
nonceReceived := false
|
||||
|
||||
for k, rv := range kvs {
|
||||
v := rv
|
||||
|
||||
switch k {
|
||||
case "username":
|
||||
h.Username = &v
|
||||
|
||||
case "realm":
|
||||
h.Realm = &v
|
||||
h.Realm = v
|
||||
realmReceived = true
|
||||
|
||||
case "nonce":
|
||||
h.Nonce = &v
|
||||
|
||||
case "uri":
|
||||
h.URI = &v
|
||||
|
||||
case "response":
|
||||
h.Response = &v
|
||||
h.Nonce = v
|
||||
nonceReceived = true
|
||||
|
||||
case "opaque":
|
||||
h.Opaque = &v
|
||||
@@ -113,58 +125,34 @@ func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !realmReceived || !nonceReceived {
|
||||
return fmt.Errorf("one or more digest fields are missing")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal encodes an Authenticate or a WWW-Authenticate header.
|
||||
// Marshal encodes a WWW-Authenticate header.
|
||||
func (h Authenticate) Marshal() base.HeaderValue {
|
||||
ret := ""
|
||||
|
||||
switch h.Method {
|
||||
case AuthBasic:
|
||||
ret += "Basic"
|
||||
|
||||
case AuthDigest:
|
||||
ret += "Digest"
|
||||
if h.Method == AuthBasic {
|
||||
return base.HeaderValue{"Basic " +
|
||||
"realm=\"" + h.Realm + "\""}
|
||||
}
|
||||
|
||||
ret += " "
|
||||
|
||||
var rets []string
|
||||
|
||||
if h.Username != nil {
|
||||
rets = append(rets, "username=\""+*h.Username+"\"")
|
||||
}
|
||||
|
||||
if h.Realm != nil {
|
||||
rets = append(rets, "realm=\""+*h.Realm+"\"")
|
||||
}
|
||||
|
||||
if h.Nonce != nil {
|
||||
rets = append(rets, "nonce=\""+*h.Nonce+"\"")
|
||||
}
|
||||
|
||||
if h.URI != nil {
|
||||
rets = append(rets, "uri=\""+*h.URI+"\"")
|
||||
}
|
||||
|
||||
if h.Response != nil {
|
||||
rets = append(rets, "response=\""+*h.Response+"\"")
|
||||
}
|
||||
ret := "Digest realm=\"" + h.Realm + "\", nonce=\"" + h.Nonce + "\""
|
||||
|
||||
if h.Opaque != nil {
|
||||
rets = append(rets, "opaque=\""+*h.Opaque+"\"")
|
||||
ret += ", opaque=\"" + *h.Opaque + "\""
|
||||
}
|
||||
|
||||
if h.Stale != nil {
|
||||
rets = append(rets, "stale=\""+*h.Stale+"\"")
|
||||
ret += ", stale=\"" + *h.Stale + "\""
|
||||
}
|
||||
|
||||
if h.Algorithm != nil {
|
||||
rets = append(rets, "algorithm=\""+*h.Algorithm+"\"")
|
||||
ret += ", algorithm=\"" + *h.Algorithm + "\""
|
||||
}
|
||||
|
||||
ret += strings.Join(rets, ", ")
|
||||
|
||||
return base.HeaderValue{ret}
|
||||
}
|
||||
|
@@ -24,82 +24,52 @@ var casesAuthenticate = []struct {
|
||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||
Authenticate{
|
||||
Method: AuthBasic,
|
||||
Realm: stringPtr("4419b63f5e51"),
|
||||
Realm: "4419b63f5e51",
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest request 1",
|
||||
"digest 1",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Realm: stringPtr("4419b63f5e51"),
|
||||
Nonce: stringPtr("8b84a3b789283a8bea8da7fa7d41f08b"),
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||
Stale: stringPtr("FALSE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest request 2",
|
||||
"digest 2",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Realm: stringPtr("4419b63f5e51"),
|
||||
Nonce: stringPtr("8b84a3b789283a8bea8da7fa7d41f08b"),
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "8b84a3b789283a8bea8da7fa7d41f08b",
|
||||
Stale: stringPtr("FALSE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest request 3",
|
||||
"digest 3",
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Realm: stringPtr("4419b63f5e51"),
|
||||
Nonce: stringPtr("133767111917411116111311118211673010032"),
|
||||
Realm: "4419b63f5e51",
|
||||
Nonce: "133767111917411116111311118211673010032",
|
||||
Stale: stringPtr("FALSE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest response generic",
|
||||
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
||||
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Username: stringPtr("aa"),
|
||||
Realm: stringPtr("bb"),
|
||||
Nonce: stringPtr("cc"),
|
||||
URI: stringPtr("dd"),
|
||||
Response: stringPtr("ee"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest response with empty field",
|
||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Username: stringPtr(""),
|
||||
Realm: stringPtr("IPCAM"),
|
||||
Nonce: stringPtr("5d17cd12b9fa8a85ac5ceef0926ea5a6"),
|
||||
URI: stringPtr("rtsp://localhost:8554/mystream"),
|
||||
Response: stringPtr("c072ae90eb4a27f4cdcb90d62266b2a1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest response with no spaces and additional fields",
|
||||
"digest after failed auth",
|
||||
base.HeaderValue{`Digest realm="Please log in with a valid username",` +
|
||||
`nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
|
||||
base.HeaderValue{`Digest realm="Please log in with a valid username", ` +
|
||||
`nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
|
||||
Authenticate{
|
||||
Method: AuthDigest,
|
||||
Realm: stringPtr("Please log in with a valid username"),
|
||||
Nonce: stringPtr("752a62306daf32b401a41004555c7663"),
|
||||
Realm: "Please log in with a valid username",
|
||||
Nonce: "752a62306daf32b401a41004555c7663",
|
||||
Opaque: stringPtr(""),
|
||||
Stale: stringPtr("FALSE"),
|
||||
Algorithm: stringPtr("MD5"),
|
||||
@@ -118,46 +88,6 @@ func TestAuthenticateUnmarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutenticatehUnmarshalErrors(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
hv base.HeaderValue
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
base.HeaderValue{},
|
||||
"value not provided",
|
||||
},
|
||||
{
|
||||
"2 values",
|
||||
base.HeaderValue{"a", "b"},
|
||||
"value provided multiple times ([a b])",
|
||||
},
|
||||
{
|
||||
"no keys",
|
||||
base.HeaderValue{"Basic"},
|
||||
"unable to split between method and keys (Basic)",
|
||||
},
|
||||
{
|
||||
"invalid keys",
|
||||
base.HeaderValue{`Basic key1="k`},
|
||||
"apexes not closed (key1=\"k)",
|
||||
},
|
||||
{
|
||||
"invalid method",
|
||||
base.HeaderValue{"Testing key1=val1"},
|
||||
"invalid method (Testing)",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
var h Authenticate
|
||||
err := h.Unmarshal(ca.hv)
|
||||
require.EqualError(t, err, ca.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateMarshal(t *testing.T) {
|
||||
for _, ca := range casesAuthenticate {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
@@ -166,3 +96,28 @@ func TestAuthenticateMarshal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzAuthenticateUnmarshal(f *testing.F) {
|
||||
for _, ca := range casesAuthenticate {
|
||||
f.Add(ca.vin[0])
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, b string) {
|
||||
var h Authenticate
|
||||
h.Unmarshal(base.HeaderValue{b}) //nolint:errcheck
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthenticateAdditionalErrors(t *testing.T) {
|
||||
func() {
|
||||
var h Authenticate
|
||||
err := h.Unmarshal(base.HeaderValue{})
|
||||
require.Error(t, err)
|
||||
}()
|
||||
|
||||
func() {
|
||||
var h Authenticate
|
||||
err := h.Unmarshal(base.HeaderValue{"a", "b"})
|
||||
require.Error(t, err)
|
||||
}()
|
||||
}
|
||||
|
@@ -13,14 +13,37 @@ type Authorization struct {
|
||||
// authentication method
|
||||
Method AuthMethod
|
||||
|
||||
// basic user
|
||||
//
|
||||
// Basic authentication fields
|
||||
//
|
||||
|
||||
// user
|
||||
BasicUser string
|
||||
|
||||
// basic password
|
||||
// password
|
||||
BasicPass string
|
||||
|
||||
// digest values
|
||||
DigestValues Authenticate
|
||||
//
|
||||
// Digest authentication fields
|
||||
//
|
||||
|
||||
// username
|
||||
Username string
|
||||
|
||||
// realm
|
||||
Realm string
|
||||
|
||||
// nonce
|
||||
Nonce string
|
||||
|
||||
// URI
|
||||
URI string
|
||||
|
||||
// response
|
||||
Response string
|
||||
|
||||
// response
|
||||
Opaque *string
|
||||
}
|
||||
|
||||
// Unmarshal decodes an Authorization header.
|
||||
@@ -35,12 +58,24 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
|
||||
v0 := v[0]
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(v0, "Basic "):
|
||||
i := strings.IndexByte(v0, ' ')
|
||||
if i < 0 {
|
||||
return fmt.Errorf("unable to split between method and keys (%v)", v0)
|
||||
}
|
||||
method, v0 := v0[:i], v0[i+1:]
|
||||
|
||||
switch method {
|
||||
case "Basic":
|
||||
h.Method = AuthBasic
|
||||
|
||||
v0 = v0[len("Basic "):]
|
||||
case "Digest":
|
||||
h.Method = AuthDigest
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid method (%s)", method)
|
||||
}
|
||||
|
||||
if h.Method == AuthBasic {
|
||||
tmp, err := base64.StdEncoding.DecodeString(v0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value")
|
||||
@@ -52,20 +87,50 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
}
|
||||
|
||||
h.BasicUser, h.BasicPass = tmp2[0], tmp2[1]
|
||||
|
||||
case strings.HasPrefix(v0, "Digest "):
|
||||
h.Method = AuthDigest
|
||||
|
||||
var vals Authenticate
|
||||
err := vals.Unmarshal(base.HeaderValue{v0})
|
||||
} else { // digest
|
||||
kvs, err := keyValParse(v0, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.DigestValues = vals
|
||||
realmReceived := false
|
||||
usernameReceived := false
|
||||
nonceReceived := false
|
||||
uriReceived := false
|
||||
responseReceived := false
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid authorization header")
|
||||
for k, rv := range kvs {
|
||||
v := rv
|
||||
|
||||
switch k {
|
||||
case "realm":
|
||||
h.Realm = v
|
||||
realmReceived = true
|
||||
|
||||
case "username":
|
||||
h.Username = v
|
||||
usernameReceived = true
|
||||
|
||||
case "nonce":
|
||||
h.Nonce = v
|
||||
nonceReceived = true
|
||||
|
||||
case "uri":
|
||||
h.URI = v
|
||||
uriReceived = true
|
||||
|
||||
case "response":
|
||||
h.Response = v
|
||||
responseReceived = true
|
||||
|
||||
case "opaque":
|
||||
h.Opaque = &v
|
||||
}
|
||||
}
|
||||
|
||||
if !realmReceived || !usernameReceived || !nonceReceived || !uriReceived || !responseReceived {
|
||||
return fmt.Errorf("one or more digest fields are missing")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -73,13 +138,18 @@ func (h *Authorization) Unmarshal(v base.HeaderValue) error {
|
||||
|
||||
// Marshal encodes an Authorization header.
|
||||
func (h Authorization) Marshal() base.HeaderValue {
|
||||
switch h.Method {
|
||||
case AuthBasic:
|
||||
response := base64.StdEncoding.EncodeToString([]byte(h.BasicUser + ":" + h.BasicPass))
|
||||
|
||||
return base.HeaderValue{"Basic " + response}
|
||||
|
||||
default: // AuthDigest
|
||||
return h.DigestValues.Marshal()
|
||||
if h.Method == AuthBasic {
|
||||
return base.HeaderValue{"Basic " +
|
||||
base64.StdEncoding.EncodeToString([]byte(h.BasicUser+":"+h.BasicPass))}
|
||||
}
|
||||
|
||||
ret := "Digest " +
|
||||
"username=\"" + h.Username + "\", realm=\"" + h.Realm + "\", " +
|
||||
"nonce=\"" + h.Nonce + "\", uri=\"" + h.URI + "\", response=\"" + h.Response + "\""
|
||||
|
||||
if h.Opaque != nil {
|
||||
ret += ", opaque=\"" + *h.Opaque + "\""
|
||||
}
|
||||
|
||||
return base.HeaderValue{ret}
|
||||
}
|
||||
|
@@ -26,17 +26,38 @@ var casesAuthorization = []struct {
|
||||
},
|
||||
{
|
||||
"digest",
|
||||
base.HeaderValue{"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\""},
|
||||
base.HeaderValue{"Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\""},
|
||||
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ` +
|
||||
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||
base.HeaderValue{`Digest username="Mufasa", realm="testrealm@host.com", ` +
|
||||
`nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ` +
|
||||
`uri="/dir/index.html", response="e966c932a9242554e42c8ee200cec7f6", opaque="5ccc069c403ebaf9f0171e9517f40e41"`},
|
||||
Authorization{
|
||||
Method: AuthDigest,
|
||||
DigestValues: Authenticate{
|
||||
Method: AuthDigest,
|
||||
Realm: stringPtr("4419b63f5e51"),
|
||||
Nonce: stringPtr("8b84a3b789283a8bea8da7fa7d41f08b"),
|
||||
Stale: stringPtr("FALSE"),
|
||||
Username: "Mufasa",
|
||||
Realm: "testrealm@host.com",
|
||||
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||
URI: "/dir/index.html",
|
||||
Response: "e966c932a9242554e42c8ee200cec7f6",
|
||||
Opaque: stringPtr("5ccc069c403ebaf9f0171e9517f40e41"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"digest with empty field",
|
||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
base.HeaderValue{`Digest username="", realm="IPCAM", ` +
|
||||
`nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", ` +
|
||||
`response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||
Authorization{
|
||||
Method: AuthDigest,
|
||||
Username: "",
|
||||
Realm: "IPCAM",
|
||||
Nonce: "5d17cd12b9fa8a85ac5ceef0926ea5a6",
|
||||
URI: "rtsp://localhost:8554/mystream",
|
||||
Response: "c072ae90eb4a27f4cdcb90d62266b2a1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -51,51 +72,6 @@ func TestAuthorizationUnmarshal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationUnmarshalErrors(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
hv base.HeaderValue
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
base.HeaderValue{},
|
||||
"value not provided",
|
||||
},
|
||||
{
|
||||
"2 values",
|
||||
base.HeaderValue{"a", "b"},
|
||||
"value provided multiple times ([a b])",
|
||||
},
|
||||
{
|
||||
"invalid",
|
||||
base.HeaderValue{`Invalid`},
|
||||
"invalid authorization header",
|
||||
},
|
||||
{
|
||||
"basic invalid 1",
|
||||
base.HeaderValue{`Basic aaa`},
|
||||
"invalid value",
|
||||
},
|
||||
{
|
||||
"basic invalid 2",
|
||||
base.HeaderValue{`Basic aW52YWxpZA==`},
|
||||
"invalid value",
|
||||
},
|
||||
{
|
||||
"digest invalid",
|
||||
base.HeaderValue{`Digest test="v`},
|
||||
"apexes not closed (test=\"v)",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
var h Authorization
|
||||
err := h.Unmarshal(ca.hv)
|
||||
require.EqualError(t, err, ca.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationMarshal(t *testing.T) {
|
||||
for _, ca := range casesAuthorization {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
@@ -104,3 +80,28 @@ func TestAuthorizationMarshal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzAuthorizationUnmarshal(f *testing.F) {
|
||||
for _, ca := range casesAuthorization {
|
||||
f.Add(ca.vin[0])
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, b string) {
|
||||
var h Authorization
|
||||
h.Unmarshal(base.HeaderValue{b}) //nolint:errcheck
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizationAdditionalErrors(t *testing.T) {
|
||||
func() {
|
||||
var h Authorization
|
||||
err := h.Unmarshal(base.HeaderValue{})
|
||||
require.Error(t, err)
|
||||
}()
|
||||
|
||||
func() {
|
||||
var h Authorization
|
||||
err := h.Unmarshal(base.HeaderValue{"a", "b"})
|
||||
require.Error(t, err)
|
||||
}()
|
||||
}
|
||||
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/593a6affc7d1fcee
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/593a6affc7d1fcee
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Basic =\"")
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/5fb42e6dcbfb41cb
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/5fb42e6dcbfb41cb
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Digest ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/771e938e4458e983
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/771e938e4458e983
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("0")
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/8643a79bab153d1b
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/8643a79bab153d1b
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string(" ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/9a1c51bbe1216ed5
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/9a1c51bbe1216ed5
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Digest =\"")
|
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/c44cf6083afc355b
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthenticateUnmarshal/c44cf6083afc355b
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Basic ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/5fb42e6dcbfb41cb
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/5fb42e6dcbfb41cb
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Digest ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/771e938e4458e983
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/771e938e4458e983
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("0")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/8643a79bab153d1b
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/8643a79bab153d1b
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string(" ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/9a1c51bbe1216ed5
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/9a1c51bbe1216ed5
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Digest =\"")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/c44cf6083afc355b
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/c44cf6083afc355b
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Basic ")
|
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/e77c46eef8802889
vendored
Normal file
2
pkg/headers/testdata/fuzz/FuzzAuthorizationUnmarshal/e77c46eef8802889
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("Basic 0")
|
Reference in New Issue
Block a user