Skip to content
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
25 changes: 25 additions & 0 deletions oauthex/resource_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type Challenge struct {
Scheme string
// Params is a map of authentication parameters.
// Keys are case-insensitive. Parsed keys are always lower-case.
// A challenge that carries a token68 instead of parameters has no Params.
Params map[string]string
}

Expand Down Expand Up @@ -255,6 +256,11 @@ func parseSingleChallenge(s string) (Challenge, error) {
if !found {
return c, nil
}
if isToken68(strings.TrimSpace(paramsStr)) {
// The challenge carries a token68 (for example, Negotiate with a
// SPNEGO token) rather than auth-params. It has no parameters.
return c, nil
}

params := make(map[string]string)
paramsStr = trimEmptyListElements(paramsStr)
Expand Down Expand Up @@ -339,3 +345,22 @@ func trimEmptyListElements(s string) string {
return r == ',' || unicode.IsSpace(r)
})
}

// isToken68 reports whether s is a token68 (RFC 9110, section 11.2):
//
// token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
func isToken68(s string) bool {
body := strings.TrimRight(s, "=")
if body == "" {
return false
}
for _, r := range body {
switch {
case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
case strings.ContainsRune("-._~+/", r):
default:
return false
}
}
return true
}
35 changes: 33 additions & 2 deletions oauthex/resource_meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,27 @@ func TestParseWWWAuthenticateEmptyListElements(t *testing.T) {
}
}

// A challenge may carry a token68 instead of auth-params (RFC 9110,
// section 11.6.1). It must not stop the Bearer challenge next to it from
// being parsed.
func TestParseWWWAuthenticateToken68(t *testing.T) {
got, err := ParseWWWAuthenticate([]string{
`Negotiate oYH1MIHyoAMKAQ, Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"`,
})
if err != nil {
t.Fatal(err)
}
want := []Challenge{
{Scheme: "negotiate"},
{Scheme: "bearer", Params: map[string]string{
"resource_metadata": "https://example.com/.well-known/oauth-protected-resource",
}},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("ParseWWWAuthenticate() = %+v, want %+v", got, want)
}
}

func TestSplitChallengesError(t *testing.T) {
if _, err := splitChallenges(`"Bearer"`); err == nil {
t.Fatal("got nil, want error")
Expand Down Expand Up @@ -300,8 +321,18 @@ func TestParseSingleChallenge(t *testing.T) {
wantErr: false,
},
{
name: "malformed param - no value",
input: "Bearer realm=",
name: "token68",
input: "Negotiate oYH1MIHyoAMKAQ",
want: Challenge{Scheme: "negotiate"},
},
{
name: "token68 with padding",
input: "Basic dGVzdA==",
want: Challenge{Scheme: "basic"},
},
{
name: "malformed token68",
input: "Negotiate abc def",
wantErr: true,
},
{
Expand Down
Loading