Skip to content
Closed
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
11 changes: 7 additions & 4 deletions oauthex/resource_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ func splitChallenges(header string) ([]string, error) {
// This is a potential challenge separator.
// A new challenge does not start with `key=value`.
// We check if the part after the comma looks like a parameter.
lookahead := strings.TrimSpace(header[i+1:])
// Empty list elements are skipped (RFC 9110, section 5.6.1).
lookahead := strings.TrimLeft(header[i+1:], ", \t")
eqPos := strings.Index(lookahead, "=")

isParam := false
Expand Down Expand Up @@ -240,7 +241,8 @@ func parseSingleChallenge(s string) (Challenge, error) {

params := make(map[string]string)

// Parse the key-value parameters.
// Parse the key-value parameters, skipping any leading empty list elements.
paramsStr = strings.TrimLeft(paramsStr, ", \t")
for paramsStr != "" {
// Find the end of the parameter key.
keyEnd := strings.Index(paramsStr, "=")
Expand Down Expand Up @@ -299,9 +301,10 @@ func parseSingleChallenge(s string) (Challenge, error) {
// Per RFC 9110, parameter keys are case-insensitive.
params[strings.ToLower(key)] = value

// If there is a comma, consume it and continue to the next parameter.
// If there is a comma, consume it, along with any empty list elements
// that follow it, and continue to the next parameter.
if strings.HasPrefix(paramsStr, ",") {
paramsStr = strings.TrimSpace(paramsStr[1:])
paramsStr = strings.TrimLeft(paramsStr, ", \t")
} else if paramsStr != "" {
// If there's content but it's not a new parameter, the format is wrong.
return Challenge{}, fmt.Errorf("malformed auth parameter: expected comma after value, but got %q", paramsStr)
Expand Down
32 changes: 32 additions & 0 deletions oauthex/resource_meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ func TestSplitChallenges(t *testing.T) {
input: `Basic realm="C:\\", Bearer error="insufficient_scope"`,
want: []string{`Basic realm="C:\\"`, ` Bearer error="insufficient_scope"`},
},
{
name: "empty list element between params",
input: `Bearer realm="example",, error="insufficient_scope"`,
want: []string{`Bearer realm="example",, error="insufficient_scope"`},
},
{
name: "empty list element between challenges",
input: `Basic, , Bearer realm="example"`,
want: []string{`Basic`, ` `, ` Bearer realm="example"`},
},
{
name: "empty input",
input: "",
Expand Down Expand Up @@ -117,6 +127,28 @@ func TestParseWWWAuthenticateEmptyQuotedValue(t *testing.T) {
}
}

// Recipients must accept empty list elements (RFC 9110, section 5.6.1).
// An extra comma must not move error="insufficient_scope" out of the Bearer
// challenge, or the auth handler would not start step-up authorization.
func TestParseWWWAuthenticateEmptyListElements(t *testing.T) {
got, err := ParseWWWAuthenticate([]string{
`, Basic realm="a",, Bearer , realm="b",, error="insufficient_scope",`,
})
if err != nil {
t.Fatal(err)
}
want := []Challenge{
{Scheme: "basic", Params: map[string]string{"realm": "a"}},
{Scheme: "bearer", Params: map[string]string{
"realm": "b",
"error": "insufficient_scope",
}},
}
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
Loading