diff --git a/access_error_test.go b/access_error_test.go index 88d511898..9cd172bff 100644 --- a/access_error_test.go +++ b/access_error_test.go @@ -92,14 +92,14 @@ func TestWriteAccessError_RFC6749(t *testing.T) { expectDescription := c.err.Description if c.err.Hint != "" { - expectDescription += "\n\n" + c.err.Hint + expectDescription += " " + c.err.Hint } if !c.debug { assert.Equal(t, expectDescription, params.Description) assert.Empty(t, params.Debug) } else { - assert.Equal(t, expectDescription+"\n\n"+c.expectDebugMessage, params.Description) + assert.Equal(t, expectDescription+" "+c.expectDebugMessage, params.Description) assert.Equal(t, c.expectDebugMessage, params.Debug) } }) diff --git a/access_request_handler.go b/access_request_handler.go index 6e6ceecae..c32a70493 100644 --- a/access_request_handler.go +++ b/access_request_handler.go @@ -59,7 +59,7 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session accessRequest := NewAccessRequest(session) if r.Method != "POST" { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return accessRequest, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s', expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 { @@ -75,7 +75,7 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session accessRequest.SetRequestedAudience(GetAudiences(r.PostForm)) accessRequest.GrantTypes = RemoveEmpty(strings.Split(r.PostForm.Get("grant_type"), " ")) if len(accessRequest.GrantTypes) < 1 { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint(`Request parameter "grant_type"" is missing`)) + return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint("Request parameter 'grant_type' is missing")) } client, err := f.AuthenticateClient(ctx, r, r.PostForm) diff --git a/audience_strategy.go b/audience_strategy.go index ada242daf..9c9d008fc 100644 --- a/audience_strategy.go +++ b/audience_strategy.go @@ -18,14 +18,14 @@ func DefaultAudienceMatchingStrategy(haystack []string, needle []string) error { for _, n := range needle { nu, err := url.Parse(n) if err != nil { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Unable to parse requested audience "%s".`, n).WithCause(err).WithDebug(err.Error())) + return errors.WithStack(ErrInvalidRequest.WithHintf("Unable to parse requested audience '%s'.", n).WithCause(err).WithDebug(err.Error())) } var found bool for _, h := range haystack { hu, err := url.Parse(h) if err != nil { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Unable to parse whitelisted audience "%s".`, h).WithCause(err).WithDebug(err.Error())) + return errors.WithStack(ErrInvalidRequest.WithHintf("Unable to parse whitelisted audience '%s'.", h).WithCause(err).WithDebug(err.Error())) } allowedPath := strings.TrimRight(hu.Path, "/") @@ -39,7 +39,7 @@ func DefaultAudienceMatchingStrategy(haystack []string, needle []string) error { } if !found { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Requested audience "%s" has not been whitelisted by the OAuth 2.0 Client.`, n)) + return errors.WithStack(ErrInvalidRequest.WithHintf("Requested audience '%s' has not been whitelisted by the OAuth 2.0 Client.", n)) } } diff --git a/authorize_error_test.go b/authorize_error_test.go index 207772b4e..ae5952eb5 100644 --- a/authorize_error_test.go +++ b/authorize_error_test.go @@ -92,7 +92,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -110,7 +110,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -128,7 +128,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&foo=bar&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&foo=bar&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -146,7 +146,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=unsupported_grant_type&error_description=The+authorization+grant+type+is+not+supported+by+the+authorization+server&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=unsupported_grant_type&error_description=The+authorization+grant+type+is+not+supported+by+the+authorization+server.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -164,7 +164,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -182,7 +182,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -200,7 +200,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -218,7 +218,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -237,7 +237,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -256,7 +256,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) @@ -275,7 +275,7 @@ func TestWriteAuthorizeError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) diff --git a/authorize_helper.go b/authorize_helper.go index 1647ccbb9..3130539a3 100644 --- a/authorize_helper.go +++ b/authorize_helper.go @@ -75,7 +75,7 @@ func MatchRedirectURIWithClientRedirectURIs(rawurl string, client Client) (*url. } } - return nil, errors.WithStack(ErrInvalidRequest.WithHint(`The "redirect_uri" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.`)) + return nil, errors.WithStack(ErrInvalidRequest.WithHint("The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.")) } // Match a requested redirect URI against a pool of registered client URIs diff --git a/authorize_request_handler.go b/authorize_request_handler.go index 49ee96cf0..429f17c92 100644 --- a/authorize_request_handler.go +++ b/authorize_request_handler.go @@ -34,6 +34,14 @@ import ( "github.com/ory/go-convenience/stringslice" ) +func wrapSigningKeyFailure(outer *RFC6749Error, inner error) *RFC6749Error { + outer = outer.WithCause(inner).WithDebug(inner.Error()) + if e := new(RFC6749Error); errors.As(inner, &e) { + return outer.WithHintf("%s %s", outer.Reason(), e.Reason()) + } + return outer +} + func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *AuthorizeRequest) error { var scope Arguments = RemoveEmpty(strings.Split(request.Form.Get("scope"), " ")) @@ -47,25 +55,25 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut if len(request.Form.Get("request")+request.Form.Get("request_uri")) == 0 { return nil } else if len(request.Form.Get("request")) > 0 && len(request.Form.Get("request_uri")) > 0 { - return errors.WithStack(ErrInvalidRequest.WithHint(`OpenID Connect parameters "request" and "request_uri" were both given, but you can use at most one.`)) + return errors.WithStack(ErrInvalidRequest.WithHint("OpenID Connect parameters 'request' and 'request_uri' were both given, but you can use at most one.")) } oidcClient, ok := request.Client.(OpenIDConnectClient) if !ok { if len(request.Form.Get("request_uri")) > 0 { - return errors.WithStack(ErrRequestURINotSupported.WithHint(`OpenID Connect "request_uri" context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.`)) + return errors.WithStack(ErrRequestURINotSupported.WithHint("OpenID Connect 'request_uri' context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.")) } - return errors.WithStack(ErrRequestNotSupported.WithHint(`OpenID Connect "request" context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.`)) + return errors.WithStack(ErrRequestNotSupported.WithHint("OpenID Connect 'request' context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.")) } if oidcClient.GetJSONWebKeys() == nil && len(oidcClient.GetJSONWebKeysURI()) == 0 { - return errors.WithStack(ErrInvalidRequest.WithHint(`OpenID Connect "request" or "request_uri" context was given, but the OAuth 2.0 Client does not have any JSON Web Keys registered.`)) + return errors.WithStack(ErrInvalidRequest.WithHint("OpenID Connect 'request' or 'request_uri' context was given, but the OAuth 2.0 Client does not have any JSON Web Keys registered.")) } assertion := request.Form.Get("request") if location := request.Form.Get("request_uri"); len(location) > 0 { if !stringslice.Has(oidcClient.GetRequestURIs(), location) { - return errors.WithStack(ErrInvalidRequestURI.WithHintf("Request URI \"%s\" is not whitelisted by the OAuth 2.0 Client.", location)) + return errors.WithStack(ErrInvalidRequestURI.WithHintf("Request URI '%s' is not whitelisted by the OAuth 2.0 Client.", location)) } hc := f.HTTPClient @@ -75,25 +83,30 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut response, err := hc.Get(location) if err != nil { - return errors.WithStack(ErrInvalidRequestURI.WithHint(`Unable to fetch OpenID Connect request parameters from "request_uri".`).WithCause(err).WithDebug(err.Error())) + return errors.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because: %s.", err.Error()).WithCause(err).WithDebug(err.Error())) } defer response.Body.Close() if response.StatusCode != http.StatusOK { - return errors.WithStack(ErrInvalidRequestURI.WithHintf(`Unable to fetch OpenID Connect request parameters from "request_uri" because status code "%d" was expected, but got "%d".`, http.StatusOK, response.StatusCode)) + return errors.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because status code '%d' was expected, but got '%d'.", http.StatusOK, response.StatusCode)) } body, err := ioutil.ReadAll(response.Body) if err != nil { - return errors.WithStack(ErrInvalidRequestURI.WithHint(`Unable to fetch OpenID Connect request parameters from "request_uri" because error occurred during body parsing.`).WithCause(err).WithDebug(err.Error())) + return errors.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because body parsing failed with: %s.", err).WithCause(err).WithDebug(err.Error())) } assertion = string(body) } token, err := jwt.ParseWithClaims(assertion, new(jwt.MapClaims), func(t *jwt.Token) (interface{}, error) { - if oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf(`The request object uses signing algorithm %s, but the requested OAuth 2.0 Client enforces signing algorithm %s.`, t.Header["alg"], oidcClient.GetRequestObjectSigningAlgorithm())) + // request_object_signing_alg - OPTIONAL. + // JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request Objects sent to the OP. All Request Objects from this Client MUST be rejected, + // if not signed with this algorithm. Request Objects are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. This algorithm MUST + // be used both when the Request Object is passed by value (using the request parameter) and when it is passed by reference (using the request_uri parameter). + // Servers SHOULD support RS256. The value none MAY be used. The default, if omitted, is that any algorithm supported by the OP and the RP MAY be used. + if oidcClient.GetRequestObjectSigningAlgorithm() != "" && oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { + return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf("The request object uses signing algorithm '%s', but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetRequestObjectSigningAlgorithm())) } if t.Method == jwt.SigningMethodNone { @@ -104,23 +117,26 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut case *jwt.SigningMethodRSA: key, err := f.findClientPublicJWK(oidcClient, t, true) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client."), err) } return key, nil case *jwt.SigningMethodECDSA: key, err := f.findClientPublicJWK(oidcClient, t, false) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve ECDSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve ECDSA signing key from OAuth 2.0 Client."), err) } return key, nil case *jwt.SigningMethodRSAPSS: key, err := f.findClientPublicJWK(oidcClient, t, true) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client."), err) } return key, nil default: - return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf(`This request object uses unsupported signing algorithm "%s"."`, t.Header["alg"])) + return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf("This request object uses unsupported signing algorithm '%s'.", t.Header["alg"])) } }) if err != nil { @@ -139,7 +155,7 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut claims, ok := token.Claims.(*jwt.MapClaims) if !ok { - return errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to type assert claims from request object.").WithDebugf(`Got claims of type %T but expected type "*jwt.MapClaims".`, token.Claims)) + return errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to type assert claims from request object.").WithDebugf(`Got claims of type %T but expected type '*jwt.MapClaims'.`, token.Claims)) } for k, v := range *claims { @@ -153,11 +169,12 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut } } + request.State = request.Form.Get("state") request.Form.Set("scope", strings.Join(claimScope, " ")) return nil } -func (f *Fosite) validateAuthorizeRedirectURI(r *http.Request, request *AuthorizeRequest) error { +func (f *Fosite) validateAuthorizeRedirectURI(_ *http.Request, request *AuthorizeRequest) error { // Fetch redirect URI from request rawRedirURI := request.Form.Get("redirect_uri") @@ -166,17 +183,17 @@ func (f *Fosite) validateAuthorizeRedirectURI(r *http.Request, request *Authoriz if err != nil { return err } else if !IsValidRedirectURI(redirectURI) { - return errors.WithStack(ErrInvalidRequest.WithHintf(`The redirect URI "%s" contains an illegal character (for example #) or is otherwise invalid.`, redirectURI)) + return errors.WithStack(ErrInvalidRequest.WithHintf("The redirect URI '%s' contains an illegal character (for example #) or is otherwise invalid.", redirectURI)) } request.RedirectURI = redirectURI return nil } -func (f *Fosite) validateAuthorizeScope(r *http.Request, request *AuthorizeRequest) error { +func (f *Fosite) validateAuthorizeScope(_ *http.Request, request *AuthorizeRequest) error { scope := RemoveEmpty(strings.Split(request.Form.Get("scope"), " ")) for _, permission := range scope { if !f.ScopeStrategy(request.Client.GetScopes(), permission) { - return errors.WithStack(ErrInvalidScope.WithHintf(`The OAuth 2.0 Client is not allowed to request scope "%s".`, permission)) + return errors.WithStack(ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", permission)) } } request.SetRequestedScopes(scope) @@ -192,7 +209,7 @@ func (f *Fosite) validateResponseTypes(r *http.Request, request *AuthorizeReques // response types is defined by their respective specifications. responseTypes := RemoveEmpty(strings.Split(r.Form.Get("response_type"), " ")) if len(responseTypes) == 0 { - return errors.WithStack(ErrUnsupportedResponseType.WithHint(`The request is missing the "response_type"" parameter.`)) + return errors.WithStack(ErrUnsupportedResponseType.WithHint("`The request is missing the 'response_type' parameter.")) } var found bool @@ -204,7 +221,7 @@ func (f *Fosite) validateResponseTypes(r *http.Request, request *AuthorizeReques } if !found { - return errors.WithStack(ErrUnsupportedResponseType.WithHintf("The client is not allowed to request response_type \"%s\".", r.Form.Get("response_type"))) + return errors.WithStack(ErrUnsupportedResponseType.WithHintf("The client is not allowed to request response_type '%s'.", r.Form.Get("response_type"))) } request.ResponseTypes = responseTypes @@ -225,8 +242,7 @@ func (f *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (Auth request.Form = r.Form // Save state to the request to be returned in error conditions (https://github.com/ory/hydra/issues/1642) - state := request.Form.Get("state") - request.State = state + request.State = request.Form.Get("state") client, err := f.Store.GetClient(ctx, request.GetRequestForm().Get("client_id")) if err != nil { @@ -264,9 +280,9 @@ func (f *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (Auth // // https://tools.ietf.org/html/rfc6819#section-4.4.1.8 // The "state" parameter should not be guessable - if len(state) < f.GetMinParameterEntropy() { + if len(request.State) < f.GetMinParameterEntropy() { // We're assuming that using less then, by default, 8 characters for the state can not be considered "unguessable" - return request, errors.WithStack(ErrInvalidState.WithHintf(`Request parameter "state" must be at least be %d characters long to ensure sufficient entropy.`, f.GetMinParameterEntropy())) + return request, errors.WithStack(ErrInvalidState.WithHintf("Request parameter 'state' must be at least be %d characters long to ensure sufficient entropy.", f.GetMinParameterEntropy())) } return request, nil diff --git a/authorize_request_handler_oidc_request_test.go b/authorize_request_handler_oidc_request_test.go index 84b8f75cb..26e129a5d 100644 --- a/authorize_request_handler_oidc_request_test.go +++ b/authorize_request_handler_oidc_request_test.go @@ -31,6 +31,8 @@ import ( "net/url" "testing" + "github.com/pkg/errors" + jwt "github.com/dgrijalva/jwt-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,7 +41,9 @@ import ( func mustGenerateAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string { token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - token.Header["kid"] = kid + if kid != "" { + token.Header["kid"] = kid + } tokenString, err := token.SignedString(key) require.NoError(t, err) return tokenString @@ -76,7 +80,8 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { } validRequestObject := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}, key, "kid-foo") - validNoneRequestObject := mustGenerateNoneAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}) + validRequestObjectWithoutKid := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}, key, "") + validNoneRequestObject := mustGenerateNoneAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz", "state": "some-state"}) var reqH http.HandlerFunc = func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte(validRequestObject)) @@ -96,8 +101,9 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { form url.Values d string - expectErr error - expectForm url.Values + expectErr error + expectErrReason string + expectForm url.Values }{ { d: "should pass because no request context given and not openid", @@ -144,18 +150,20 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { expectForm: url.Values{"scope": {"openid"}}, }, { - d: "should fail because kid does not exist", - form: url.Values{"scope": {"openid"}, "request": {mustGenerateAssertion(t, jwt.MapClaims{}, key, "does-not-exists")}}, - client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, - expectErr: ErrInvalidRequestObject, - expectForm: url.Values{"scope": {"openid"}}, + d: "should fail because kid does not exist", + form: url.Values{"scope": {"openid"}, "request": {mustGenerateAssertion(t, jwt.MapClaims{}, key, "does-not-exists")}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + expectErr: ErrInvalidRequestObject, + expectErrReason: "Unable to retrieve RSA signing key from OAuth 2.0 Client. The JSON Web Token uses signing key with kid 'does-not-exists', which could not be found.", + expectForm: url.Values{"scope": {"openid"}}, }, { - d: "should fail because not RS256 token", - form: url.Values{"scope": {"openid"}, "request": {mustGenerateHSAssertion(t, jwt.MapClaims{})}}, - client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, - expectErr: ErrInvalidRequestObject, - expectForm: url.Values{"scope": {"openid"}}, + d: "should fail because not RS256 token", + form: url.Values{"scope": {"openid"}, "request": {mustGenerateHSAssertion(t, jwt.MapClaims{})}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + expectErr: ErrInvalidRequestObject, + expectErrReason: "The request object uses signing algorithm 'HS256', but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.", + expectForm: url.Values{"scope": {"openid"}}, }, { d: "should pass and set request parameters properly", @@ -163,6 +171,12 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, expectForm: url.Values{"scope": {"foo openid"}, "request": {validRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, }, + { + d: "should pass even if kid is unset", + form: url.Values{"scope": {"openid"}, "request": {validRequestObjectWithoutKid}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + expectForm: url.Values{"scope": {"foo openid"}, "request": {validRequestObjectWithoutKid}, "foo": {"bar"}, "baz": {"baz"}}, + }, { d: "should fail because request uri is not whitelisted", form: url.Values{"scope": {"openid"}, "request_uri": {reqTS.URL}}, @@ -180,7 +194,13 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { d: "should pass when request object uses algorithm none", form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}}, client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL, RequestObjectSigningAlgorithm: "none"}, - expectForm: url.Values{"scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, + expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, + }, + { + d: "should pass when request object uses algorithm none and the client did not explicitly allow any algorithm", + form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}}, + client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL}, + expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, }, } { t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) { @@ -194,6 +214,11 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { err := f.authorizeRequestParametersFromOpenIDConnectRequest(req) if tc.expectErr != nil { require.EqualError(t, err, tc.expectErr.Error(), "%+v", err) + if tc.expectErrReason != "" { + real := new(RFC6749Error) + require.True(t, errors.As(err, &real)) + assert.EqualValues(t, real.Reason(), tc.expectErrReason) + } } else { require.NoError(t, err) require.Equal(t, len(tc.expectForm), len(req.Form)) diff --git a/client_authentication.go b/client_authentication.go index ca3d3553c..20bee350a 100644 --- a/client_authentication.go +++ b/client_authentication.go @@ -68,7 +68,7 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if assertionType := form.Get("client_assertion_type"); assertionType == clientAssertionJWTBearerType { assertion := form.Get("client_assertion") if len(assertion) == 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of \"%s\".", clientAssertionJWTBearerType)) + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of '%s'.", clientAssertionJWTBearerType)) } var clientID string @@ -83,9 +83,9 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if clientID == "" { if claims, ok := t.Claims.(*jwt.MapClaims); !ok { - return nil, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to type assert claims from client_assertion.").WithDebugf(`Expected claims to be of type "*jwt.MapClaims" but got "%T".`, t.Claims)) + return nil, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to type assert claims from client_assertion.").WithDebugf(`Expected claims to be of type '*jwt.MapClaims' but got '%T'.`, t.Claims)) } else if sub, ok := (*claims)["sub"].(string); !ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint(`The claim "sub" from the client_assertion JSON Web Token is undefined.`)) + return nil, errors.WithStack(ErrInvalidClient.WithHint("The claim 'sub' from the client_assertion JSON Web Token is undefined.")) } else { clientID = sub } @@ -105,19 +105,19 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u case "private_key_jwt": break case "none": - return nil, errors.WithStack(ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however \"client_assertion\" was provided in the request.")) + return nil, errors.WithStack(ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however 'client_assertion' was provided in the request.")) case "client_secret_post": fallthrough case "client_secret_basic": - return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method \"%s\", however \"client_assertion\" was provided in the request.", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however 'client_assertion' was provided in the request.", oidcClient.GetTokenEndpointAuthMethod())) case "client_secret_jwt": fallthrough default: - return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method \"%s\", however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod())) } if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { - return nil, errors.WithStack(ErrInvalidClient.WithHintf(`The "client_assertion" uses signing algorithm "%s", but the requested OAuth 2.0 Client enforces signing algorithm "%s".`, t.Header["alg"], oidcClient.GetTokenEndpointAuthSigningAlgorithm())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetTokenEndpointAuthSigningAlgorithm())) } if _, ok := t.Method.(*jwt.SigningMethodRSA); ok { @@ -127,10 +127,10 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } else if _, ok := t.Method.(*jwt.SigningMethodRSAPSS); ok { return f.findClientPublicJWK(oidcClient, t, true) } else if _, ok := t.Method.(*jwt.SigningMethodHMAC); ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint("This authorization server does not support client authentication method \"client_secret_jwt\".")) + return nil, errors.WithStack(ErrInvalidClient.WithHint("This authorization server does not support client authentication method 'client_secret_jwt'.")) } - return nil, errors.WithStack(ErrInvalidClient.WithHintf(`The "client_assertion" request parameter uses unsupported signing algorithm "%s".`, t.Header["alg"])) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header["alg"])) }) if err != nil { // Do not re-process already enhanced errors @@ -139,7 +139,7 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if e.Inner != nil { return nil, e.Inner } - return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to verify the integrity of the \"client_assertion\" value.").WithCause(err).WithDebug(err.Error())) + return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to verify the integrity of the 'client_assertion' value.").WithCause(err).WithDebug(err.Error())) } return nil, err } else if err := token.Claims.Valid(); err != nil { @@ -148,20 +148,20 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u claims, ok := token.Claims.(*jwt.MapClaims) if !ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to type assert claims from request parameter \"client_assertion\".").WithDebugf(`Got claims of type %T but expected type "*jwt.MapClaims".`, token.Claims)) + return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to type assert claims from request parameter 'client_assertion'.").WithDebugf("Got claims of type %T but expected type '*jwt.MapClaims'.", token.Claims)) } var jti string if !claims.VerifyIssuer(clientID, true) { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"iss\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client.")) + return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim 'iss' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) } else if f.TokenURL == "" { return nil, errors.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set.")) } else if sub, ok := (*claims)["sub"].(string); !ok || sub != clientID { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"sub\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client.")) + return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim 'sub' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) } else if jti, ok = (*claims)["jti"].(string); !ok || len(jti) == 0 { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"jti\" from \"client_assertion\" must be set but is not.")) + return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim 'jti' from 'client_assertion' must be set but is not.")) } else if f.Store.ClientAssertionJWTValid(context.Background(), jti) != nil { - return nil, errors.WithStack(ErrJTIKnown.WithHint("Claim \"jti\" from \"client_assertion\" MUST only be used once.")) + return nil, errors.WithStack(ErrJTIKnown.WithHint("Claim 'jti' from 'client_assertion' MUST only be used once.")) } // type conversion according to jwt.MapClaims.VerifyExpiresAt @@ -185,7 +185,7 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if auds, ok := (*claims)["aud"].([]interface{}); !ok { if !claims.VerifyAudience(f.TokenURL, true) { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim \"audience\" from \"client_assertion\" must match the authorization server's token endpoint \"%s\".", f.TokenURL)) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL)) } } else { var found bool @@ -197,13 +197,13 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } if !found { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim \"audience\" from \"client_assertion\" must match the authorization server's token endpoint \"%s\".", f.TokenURL)) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL)) } } return client, nil } else if len(assertionType) > 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unknown client_assertion_type \"%s\".", assertionType)) + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unknown client_assertion_type '%s'.", assertionType)) } clientID, clientSecret, err := clientCredentialsFromRequest(r, form) @@ -219,11 +219,11 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if oidcClient, ok := client.(OpenIDConnectClient); !ok { // If this isn't an OpenID Connect client then we actually don't care about any of this, just continue! } else if ok && form.Get("client_id") != "" && form.Get("client_secret") != "" && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_post" { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"client_secret_post\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"client_secret_post\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.", oidcClient.GetTokenEndpointAuthMethod())) } else if _, _, basicOk := r.BasicAuth(); basicOk && ok && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_basic" { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"client_secret_basic\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"client_secret_basic\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_basic' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_basic'.", oidcClient.GetTokenEndpointAuthMethod())) } else if ok && oidcClient.GetTokenEndpointAuthMethod() != "none" && client.IsPublic() { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"none\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"none\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'none' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'none'.", oidcClient.GetTokenEndpointAuthMethod())) } if client.IsPublic() { @@ -239,14 +239,18 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (interface{}, error) { + keys := set.Keys + if len(keys) == 0 { + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The retrieved JSON Web Key Set does not contain any keys.")) + } + kid, ok := t.Header["kid"].(string) - if !ok { - return nil, errors.WithStack(ErrInvalidRequest.WithHint("The JSON Web Token must contain a kid header value but did not.")) + if ok { + keys = set.Key(kid) } - keys := set.Key(kid) if len(keys) == 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid \"%s\", which could not be found.", kid)) + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid '%s', which could not be found.", kid)) } for _, key := range keys { @@ -265,9 +269,9 @@ func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (i } if expectsRSAKey { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find RSA public key with use=\"sig\" for kid \"%s\" in JSON Web Key Set.", kid)) + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find RSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid)) } else { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use=\"sig\" for kid \"%s\" in JSON Web Key Set.", kid)) + return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid)) } } @@ -275,9 +279,9 @@ func clientCredentialsFromRequest(r *http.Request, form url.Values) (clientID, c if id, secret, ok := r.BasicAuth(); !ok { return clientCredentialsFromRequestBody(form, true) } else if clientID, err = url.QueryUnescape(id); err != nil { - return "", "", errors.WithStack(ErrInvalidRequest.WithHint(`The client id in the HTTP authorization header could not be decoded from "application/x-www-form-urlencoded".`).WithCause(err).WithDebug(err.Error())) + return "", "", errors.WithStack(ErrInvalidRequest.WithHint("The client id in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithCause(err).WithDebug(err.Error())) } else if clientSecret, err = url.QueryUnescape(secret); err != nil { - return "", "", errors.WithStack(ErrInvalidRequest.WithHint(`The client secret in the HTTP authorization header could not be decoded from "application/x-www-form-urlencoded".`).WithCause(err).WithDebug(err.Error())) + return "", "", errors.WithStack(ErrInvalidRequest.WithHint("The client secret in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithCause(err).WithDebug(err.Error())) } return clientID, clientSecret, nil diff --git a/client_authentication_jwks_strategy.go b/client_authentication_jwks_strategy.go index 9d54ca0e7..11ee3ab34 100644 --- a/client_authentication_jwks_strategy.go +++ b/client_authentication_jwks_strategy.go @@ -60,17 +60,17 @@ func (s *DefaultJWKSFetcherStrategy) Resolve(location string, forceRefresh bool) if !ok || forceRefresh { response, err := s.client.Get(location) if err != nil { - return nil, errors.WithStack(ErrServerError.WithHintf(`Unable to fetch JSON Web Keys from location "%s".`, location).WithCause(err).WithDebug(err.Error())) + return nil, errors.WithStack(ErrServerError.WithHintf("Unable to fetch JSON Web Keys from location '%s'. Check for typos or other network issues.", location).WithCause(err).WithDebug(err.Error())) } defer response.Body.Close() if response.StatusCode < 200 || response.StatusCode >= 400 { - return nil, errors.WithStack(ErrServerError.WithHintf(`Expected successful status code from location "%s", but received code "%d".`, location, response.StatusCode)) + return nil, errors.WithStack(ErrServerError.WithHintf("Expected successful status code in range of 200 - 399 from location '%s' but received code %d.", location, response.StatusCode)) } var set jose.JSONWebKeySet if err := json.NewDecoder(response.Body).Decode(&set); err != nil { - return nil, errors.WithStack(ErrServerError.WithHintf("Unable to decode JSON Web Keys from location \"%s\".", location).WithCause(err).WithDebug(err.Error())) + return nil, errors.WithStack(ErrServerError.WithHintf("Unable to decode JSON Web Keys from location '%s'. Please check for typos and if the URL returns valid JSON.", location).WithCause(err).WithDebug(err.Error())) } s.keys[location] = set diff --git a/errors.go b/errors.go index 96fbf2826..f1bf63983 100644 --- a/errors.go +++ b/errors.go @@ -26,6 +26,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "github.com/pkg/errors" ) @@ -39,167 +40,167 @@ var ( ErrSerializationFailure = errors.New("The request could not be completed due to concurrent access") ErrUnknownRequest = &RFC6749Error{ Name: errUnknownErrorName, - Description: "The handler is not responsible for this request", + Description: "The handler is not responsible for this request.", Code: http.StatusBadRequest, } ErrRequestForbidden = &RFC6749Error{ Name: errRequestForbidden, - Description: "The request is not allowed", + Description: "The request is not allowed.", Hint: "You are not allowed to perform this action.", Code: http.StatusForbidden, } ErrInvalidRequest = &RFC6749Error{ Name: errInvalidRequestName, - Description: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed", + Description: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.", Hint: "Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.", Code: http.StatusBadRequest, } ErrUnauthorizedClient = &RFC6749Error{ Name: errUnauthorizedClientName, - Description: "The client is not authorized to request a token using this method", + Description: "The client is not authorized to request a token using this method.", Hint: "Make sure that client id and secret are correctly specified and that the client exists.", Code: http.StatusBadRequest, } ErrAccessDenied = &RFC6749Error{ Name: errAccessDeniedName, - Description: "The resource owner or authorization server denied the request", + Description: "The resource owner or authorization server denied the request.", Hint: "Make sure that the request you are making is valid. Maybe the credential or request parameters you are using are limited in scope or otherwise restricted.", Code: http.StatusForbidden, } ErrUnsupportedResponseType = &RFC6749Error{ Name: errUnsupportedResponseTypeName, - Description: "The authorization server does not support obtaining a token using this method", + Description: "The authorization server does not support obtaining a token using this method.", Code: http.StatusBadRequest, } ErrInvalidScope = &RFC6749Error{ Name: errInvalidScopeName, - Description: "The requested scope is invalid, unknown, or malformed", + Description: "The requested scope is invalid, unknown, or malformed.", Code: http.StatusBadRequest, } ErrServerError = &RFC6749Error{ Name: errServerErrorName, - Description: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request", + Description: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.", Code: http.StatusInternalServerError, } ErrTemporarilyUnavailable = &RFC6749Error{ Name: errTemporarilyUnavailableName, - Description: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server", + Description: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.", Code: http.StatusServiceUnavailable, } ErrUnsupportedGrantType = &RFC6749Error{ Name: errUnsupportedGrantTypeName, - Description: "The authorization grant type is not supported by the authorization server", + Description: "The authorization grant type is not supported by the authorization server.", Code: http.StatusBadRequest, } ErrInvalidGrant = &RFC6749Error{ Name: errInvalidGrantName, - Description: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client", + Description: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.", Code: http.StatusBadRequest, } ErrInvalidClient = &RFC6749Error{ Name: errInvalidClientName, - Description: "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)", + Description: "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).", Code: http.StatusUnauthorized, } ErrInvalidState = &RFC6749Error{ Name: errInvalidStateName, - Description: "The state is missing or does not have enough characters and is therefore considered too weak", + Description: "The state is missing or does not have enough characters and is therefore considered too weak.", Code: http.StatusBadRequest, } ErrMisconfiguration = &RFC6749Error{ Name: errMisconfigurationName, - Description: "The request failed because of an internal error that is probably caused by misconfiguration", + Description: "The request failed because of an internal error that is probably caused by misconfiguration.", Code: http.StatusInternalServerError, } ErrInsufficientEntropy = &RFC6749Error{ Name: errInsufficientEntropyName, - Description: "The request used a security parameter (e.g., anti-replay, anti-csrf) with insufficient entropy", + Description: "The request used a security parameter (e.g., anti-replay, anti-csrf) with insufficient entropy.", Code: http.StatusBadRequest, } ErrNotFound = &RFC6749Error{ Name: errNotFoundName, - Description: "Could not find the requested resource(s)", + Description: "Could not find the requested resource(s).", Code: http.StatusNotFound, } ErrRequestUnauthorized = &RFC6749Error{ Name: errRequestUnauthorizedName, - Description: "The request could not be authorized", + Description: "The request could not be authorized.", Hint: "Check that you provided valid credentials in the right format.", Code: http.StatusUnauthorized, } ErrTokenSignatureMismatch = &RFC6749Error{ Name: errTokenSignatureMismatchName, - Description: "Token signature mismatch", + Description: "Token signature mismatch.", Hint: "Check that you provided a valid token in the right format.", Code: http.StatusBadRequest, } ErrInvalidTokenFormat = &RFC6749Error{ Name: errInvalidTokenFormatName, - Description: "Invalid token format", + Description: "Invalid token format.", Hint: "Check that you provided a valid token in the right format.", Code: http.StatusBadRequest, } ErrTokenExpired = &RFC6749Error{ Name: errTokenExpiredName, - Description: "Token expired", + Description: "Token expired.", Hint: "The token expired.", Code: http.StatusUnauthorized, } ErrScopeNotGranted = &RFC6749Error{ Name: errScopeNotGrantedName, - Description: "The token was not granted the requested scope", + Description: "The token was not granted the requested scope.", Hint: "The resource owner did not grant the requested scope.", Code: http.StatusForbidden, } ErrTokenClaim = &RFC6749Error{ Name: errTokenClaimName, - Description: "The token failed validation due to a claim mismatch", + Description: "The token failed validation due to a claim mismatch.", Hint: "One or more token claims failed validation.", Code: http.StatusUnauthorized, } ErrInactiveToken = &RFC6749Error{ Name: errTokenInactiveName, - Description: "Token is inactive because it is malformed, expired or otherwise invalid", + Description: "Token is inactive because it is malformed, expired or otherwise invalid.", Hint: "Token validation failed.", Code: http.StatusUnauthorized, } ErrLoginRequired = &RFC6749Error{ Name: errLoginRequired, - Description: "The Authorization Server requires End-User authentication", + Description: "The Authorization Server requires End-User authentication.", Code: http.StatusBadRequest, } ErrInteractionRequired = &RFC6749Error{ - Description: "The Authorization Server requires End-User interaction of some form to proceed", + Description: "The Authorization Server requires End-User interaction of some form to proceed.", Name: errInteractionRequired, Code: http.StatusBadRequest, } ErrConsentRequired = &RFC6749Error{ - Description: "The Authorization Server requires End-User consent", + Description: "The Authorization Server requires End-User consent.", Name: errConsentRequired, Code: http.StatusBadRequest, } ErrRequestNotSupported = &RFC6749Error{ - Description: "The OP does not support use of the request parameter", + Description: "The OP does not support use of the request parameter.", Name: errRequestNotSupportedName, Code: http.StatusBadRequest, } ErrRequestURINotSupported = &RFC6749Error{ - Description: "The OP does not support use of the request_uri parameter", + Description: "The OP does not support use of the request_uri parameter.", Name: errRequestURINotSupportedName, Code: http.StatusBadRequest, } ErrRegistrationNotSupported = &RFC6749Error{ - Description: "The OP does not support use of the registration parameter", + Description: "The OP does not support use of the registration parameter.", Name: errRegistrationNotSupportedName, Code: http.StatusBadRequest, } ErrInvalidRequestURI = &RFC6749Error{ - Description: "The request_uri in the Authorization Request returns an error or contains invalid data. ", + Description: "The request_uri in the Authorization Request returns an error or contains invalid data.", Name: errInvalidRequestURI, Code: http.StatusBadRequest, } ErrInvalidRequestObject = &RFC6749Error{ - Description: "The request parameter contains an invalid Request Object. ", + Description: "The request parameter contains an invalid Request Object.", Name: errInvalidRequestObject, Code: http.StatusBadRequest, } @@ -253,7 +254,7 @@ func ErrorToRFC6749Error(err error) *RFC6749Error { } return &RFC6749Error{ Name: errUnknownErrorName, - Description: "The error is unrecognizable.", + Description: "The error is unrecognizable", Debug: err.Error(), Code: http.StatusInternalServerError, cause: err, @@ -339,12 +340,12 @@ func (e *RFC6749Error) Sanitize() *RFC6749Error { func (e *RFC6749Error) GetDescription() string { description := e.Description if e.Hint != "" { - description += "\n\n" + e.Hint + description += " " + e.Hint } if e.Debug != "" { - description += "\n\n" + e.Debug + description += " " + e.Debug } - return description + return strings.ReplaceAll(description, "\"", "'") } // Is returns true if the target error is equal to the current error. Used by errors.Is. diff --git a/handler/oauth2/flow_authorize_code_auth.go b/handler/oauth2/flow_authorize_code_auth.go index 5e5b1a665..82d8d225f 100644 --- a/handler/oauth2/flow_authorize_code_auth.go +++ b/handler/oauth2/flow_authorize_code_auth.go @@ -89,7 +89,7 @@ func (c *AuthorizeExplicitGrantHandler) HandleAuthorizeEndpointRequest(ctx conte client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } diff --git a/handler/oauth2/flow_authorize_implicit.go b/handler/oauth2/flow_authorize_implicit.go index 99e35ede8..729128761 100644 --- a/handler/oauth2/flow_authorize_implicit.go +++ b/handler/oauth2/flow_authorize_implicit.go @@ -59,13 +59,13 @@ func (c *AuthorizeImplicitGrantTypeHandler) HandleAuthorizeEndpointRequest(ctx c // } if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } diff --git a/handler/oauth2/flow_client_credentials.go b/handler/oauth2/flow_client_credentials.go index 385c64fdc..03cea197e 100644 --- a/handler/oauth2/flow_client_credentials.go +++ b/handler/oauth2/flow_client_credentials.go @@ -47,7 +47,7 @@ func (c *ClientCredentialsGrantHandler) HandleTokenEndpointRequest(_ context.Con client := request.GetClient() for _, scope := range request.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } @@ -59,7 +59,7 @@ func (c *ClientCredentialsGrantHandler) HandleTokenEndpointRequest(_ context.Con // This requirement is already fulfilled because fosite requires all token requests to be authenticated as described // in https://tools.ietf.org/html/rfc6749#section-3.2.1 if client.IsPublic() { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant \"client_credentials\".")) + return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant 'client_credentials'.")) } // if the client is not public, he has already been authenticated by the access request handler. @@ -74,7 +74,7 @@ func (c *ClientCredentialsGrantHandler) PopulateTokenEndpointResponse(ctx contex } if !request.GetClient().GetGrantTypes().Has("client_credentials") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"client_credentials\".")) + return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'client_credentials'.")) } return c.IssueAccessToken(ctx, request, response) diff --git a/handler/oauth2/flow_refresh.go b/handler/oauth2/flow_refresh.go index 18d93f091..77e620bda 100644 --- a/handler/oauth2/flow_refresh.go +++ b/handler/oauth2/flow_refresh.go @@ -58,7 +58,7 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex } if !request.GetClient().GetGrantTypes().Has("refresh_token") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"refresh_token\".")) + return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'refresh_token'.")) } refresh := request.GetRequestForm().Get("refresh_token") @@ -76,7 +76,7 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex if !(len(c.RefreshTokenScopes) == 0 || originalRequest.GetGrantedScopes().HasOneOf(c.RefreshTokenScopes...)) { scopeNames := strings.Join(c.RefreshTokenScopes, " or ") - hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the \"refresh_token\" authorization grant.", scopeNames) + hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the 'refresh_token' authorization grant.", scopeNames) return errors.WithStack(fosite.ErrScopeNotGranted.WithHint(hint)) } @@ -92,7 +92,7 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex for _, scope := range originalRequest.GetGrantedScopes() { if !c.ScopeStrategy(request.GetClient().GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } request.GrantScope(scope) } diff --git a/handler/oauth2/flow_resource_owner.go b/handler/oauth2/flow_resource_owner.go index 8f216b51e..62da78b97 100644 --- a/handler/oauth2/flow_resource_owner.go +++ b/handler/oauth2/flow_resource_owner.go @@ -51,13 +51,13 @@ func (c *ResourceOwnerPasswordCredentialsGrantHandler) HandleTokenEndpointReques } if !request.GetClient().GetGrantTypes().Has("password") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The client is not allowed to use authorization grant \"password\".")) + return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The client is not allowed to use authorization grant 'password'.")) } client := request.GetClient() for _, scope := range request.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } diff --git a/handler/oauth2/introspector.go b/handler/oauth2/introspector.go index d5f0bf743..19debcabc 100644 --- a/handler/oauth2/introspector.go +++ b/handler/oauth2/introspector.go @@ -71,7 +71,7 @@ func matchScopes(ss fosite.ScopeStrategy, granted, scopes []string) error { } if !ss(granted, scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The request scope \"%s\" has not been granted or is not allowed to be requested.", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The request scope '%s' has not been granted or is not allowed to be requested.", scope)) } } diff --git a/handler/oauth2/strategy_hmacsha.go b/handler/oauth2/strategy_hmacsha.go index 16d962b66..34c17b5ae 100644 --- a/handler/oauth2/strategy_hmacsha.go +++ b/handler/oauth2/strategy_hmacsha.go @@ -55,10 +55,10 @@ func (h HMACSHAStrategy) GenerateAccessToken(_ context.Context, _ fosite.Request func (h HMACSHAStrategy) ValidateAccessToken(_ context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AccessToken) if exp.IsZero() && r.GetRequestedAt().Add(h.AccessTokenLifespan).Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at \"%s\".", r.GetRequestedAt().Add(h.AccessTokenLifespan))) + return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", r.GetRequestedAt().Add(h.AccessTokenLifespan))) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at \"%s\".", exp)) + return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp)) } return h.Enigma.Validate(token) } @@ -74,7 +74,7 @@ func (h HMACSHAStrategy) ValidateRefreshToken(_ context.Context, r fosite.Reques return h.Enigma.Validate(token) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at \"%s\".", exp)) + return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp)) } return h.Enigma.Validate(token) } @@ -86,10 +86,10 @@ func (h HMACSHAStrategy) GenerateAuthorizeCode(_ context.Context, _ fosite.Reque func (h HMACSHAStrategy) ValidateAuthorizeCode(_ context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode) if exp.IsZero() && r.GetRequestedAt().Add(h.AuthorizeCodeLifespan).Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at \"%s\".", r.GetRequestedAt().Add(h.AuthorizeCodeLifespan))) + return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", r.GetRequestedAt().Add(h.AuthorizeCodeLifespan))) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at \"%s\".", exp)) + return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp)) } return h.Enigma.Validate(token) diff --git a/handler/openid/flow_hybrid.go b/handler/openid/flow_hybrid.go index b15636657..98e674ddf 100644 --- a/handler/openid/flow_hybrid.go +++ b/handler/openid/flow_hybrid.go @@ -67,7 +67,7 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. if nonce := ar.GetRequestForm().Get("nonce"); len(nonce) == 0 { return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"nonce\" must be set when using the OpenID Connect Hybrid Flow.")) } else if len(nonce) < c.MinParameterEntropy { - return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) + return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) } sess, ok := ar.GetSession().(Session) @@ -82,14 +82,14 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } claims := sess.IDTokenClaims() if ar.GetResponseTypes().Has("code") { if !ar.GetClient().GetGrantTypes().Has("authorization_code") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) + return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'authorization_code'.")) } code, signature, err := c.AuthorizeExplicitGrantHandler.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) @@ -128,7 +128,7 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. if ar.GetResponseTypes().Has("token") { if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } else if err := c.AuthorizeImplicitGrantTypeHandler.IssueImplicitAccessToken(ctx, ar, resp); err != nil { return errors.WithStack(err) } diff --git a/handler/openid/flow_implicit.go b/handler/openid/flow_implicit.go index 3f267b349..82bedf531 100644 --- a/handler/openid/flow_implicit.go +++ b/handler/openid/flow_implicit.go @@ -52,7 +52,7 @@ func (c *OpenIDConnectImplicitHandler) HandleAuthorizeEndpointRequest(ctx contex } if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } // Disabled because this is already handled at the authorize_request_handler @@ -63,15 +63,15 @@ func (c *OpenIDConnectImplicitHandler) HandleAuthorizeEndpointRequest(ctx contex //} if nonce := ar.GetRequestForm().Get("nonce"); len(nonce) == 0 { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"nonce\" must be set when using the OpenID Connect Implicit Flow.")) + return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'nonce' must be set when using the OpenID Connect Implicit Flow.")) } else if len(nonce) < c.MinParameterEntropy { - return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) + return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) } client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } diff --git a/handler/openid/strategy_jwt.go b/handler/openid/strategy_jwt.go index 850940d08..92f7737bc 100644 --- a/handler/openid/strategy_jwt.go +++ b/handler/openid/strategy_jwt.go @@ -196,15 +196,15 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R if errors.As(err, &ve) && ve.Errors == jwtgo.ValidationErrorExpired { // Expired ID Tokens are allowed as values to id_token_hint } else if err != nil { - return "", errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebugf("Unable to decode id token from id_token_hint parameter because %s.", err.Error())) + return "", errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebugf("Unable to decode id token from 'id_token_hint' parameter because %s.", err.Error())) } if hintClaims, ok := tokenHint.Claims.(jwtgo.MapClaims); !ok { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Unable to decode id token from id_token_hint to *jwt.StandardClaims.")) + return "", errors.WithStack(fosite.ErrServerError.WithDebug("Unable to decode id token from 'id_token_hint' to *jwt.StandardClaims.")) } else if hintSub, _ := hintClaims["sub"].(string); hintSub == "" { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Provided id token from id_token_hint does not have a subject.")) + return "", errors.WithStack(fosite.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject.")) } else if hintSub != claims.Subject { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from id_token_hint.")) + return "", errors.WithStack(fosite.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'.")) } } } @@ -230,7 +230,7 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R if len(nonce) == 0 { } else if len(nonce) > 0 && len(nonce) < h.MinParameterEntropy { // We're assuming that using less then, by default, 8 characters for the state can not be considered "unguessable" - return "", errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", h.MinParameterEntropy)) + return "", errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", h.MinParameterEntropy)) } claims.Nonce = nonce diff --git a/handler/openid/validator.go b/handler/openid/validator.go index 8531bee16..29c9e07f1 100644 --- a/handler/openid/validator.go +++ b/handler/openid/validator.go @@ -96,12 +96,12 @@ func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req } if !isWhitelisted(prompt, v.AllowedPrompt) { - return errors.WithStack(fosite.ErrInvalidRequest.WithHintf(`Used unknown value "%s" for prompt parameter`, prompt)) + return errors.WithStack(fosite.ErrInvalidRequest.WithHintf("Used unknown value '%s' for prompt parameter", prompt)) } if stringslice.Has(prompt, "none") && len(prompt) > 1 { // If this parameter contains none with any other value, an error is returned. - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.")) + return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed.")) } maxAge, err := strconv.ParseInt(req.GetRequestForm().Get("max_age"), 10, 64) @@ -139,13 +139,13 @@ func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because because auth_time is missing from session.")) } if claims.AuthTime.After(claims.RequestedAt) { - return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to \"none\" but auth_time happened after the authorization request was registered, indicating that the user was logged in during this request which is not allowed.")) + return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to 'none' but auth_time happened after the authorization request was registered, indicating that the user was logged in during this request which is not allowed.")) } } if stringslice.Has(prompt, "login") { if claims.AuthTime.Before(claims.RequestedAt) { - return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to \"login\" but auth_time happened before the authorization request was registered, indicating that the user was not re-authenticated which is forbidden.")) + return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to 'login' but auth_time happened before the authorization request was registered, indicating that the user was not re-authenticated which is forbidden.")) } } diff --git a/handler/pkce/handler.go b/handler/pkce/handler.go index cd97ebd00..6da8c1cf0 100644 --- a/handler/pkce/handler.go +++ b/handler/pkce/handler.go @@ -176,7 +176,7 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite WithHint("The PKCE code verifier can not be longer than 128 characters.")) } else if verifierWrongFormat.MatchString(verifier) { return errors.WithStack(fosite.ErrInvalidGrant. - WithHint(`The PKCE code verifier must only contain [a-Z] / [0-9] / "-" / "." / "_" / "~"`)) + WithHint("The PKCE code verifier must only contain [a-Z], [0-9], '-', '.', '_', '~'.")) } // Upon receipt of the request at the token endpoint, the server diff --git a/internal/introspector.go b/internal/introspector.go index 138ef9dd3..2a821e0db 100644 --- a/internal/introspector.go +++ b/internal/introspector.go @@ -9,6 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" + fosite "github.com/ory/fosite" ) diff --git a/introspection_request_handler.go b/introspection_request_handler.go index 9b5fc6750..59356ffe4 100644 --- a/introspection_request_handler.go +++ b/introspection_request_handler.go @@ -111,7 +111,7 @@ import ( // token=mF_9.B5f-4.1JqM&token_type_hint=access_token func (f *Fosite) NewIntrospectionRequest(ctx context.Context, r *http.Request, session Session) (IntrospectionResponder, error) { if r.Method != "POST" { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s' but expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 { @@ -129,7 +129,7 @@ func (f *Fosite) NewIntrospectionRequest(ctx context.Context, r *http.Request, s if tu, _, err := f.IntrospectToken(ctx, clientToken, AccessToken, session.Clone()); err != nil { return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing, malformed, or credentials used are invalid.")) } else if tu != "" && tu != AccessToken { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHintf("HTTP Authorization header did not provide a token of type \"access_token\", got type \"%s\".", tu)) + return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHintf("HTTP Authorization header did not provide a token of type 'access_token', got type '%s'.", tu)) } } else { id, secret, ok := r.BasicAuth() diff --git a/revoke_handler.go b/revoke_handler.go index 4597ca50b..3c827af21 100644 --- a/revoke_handler.go +++ b/revoke_handler.go @@ -49,7 +49,7 @@ import ( // server and does not influence the revocation response. func (f *Fosite) NewRevocationRequest(ctx context.Context, r *http.Request) error { if r.Method != "POST" { - return errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s' but expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { return errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 {