-
-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for none response_type
- Loading branch information
1 parent
44fd2cc
commit f59d68c
Showing
5 changed files
with
255 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package oauth2 | ||
|
||
import ( | ||
"context" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/ory/x/errorsx" | ||
|
||
"github.com/ory/fosite" | ||
) | ||
|
||
var _ fosite.AuthorizeEndpointHandler = (*NoneResponseTypeHandler)(nil) | ||
|
||
// NoneResponseTypeHandler is a response handler for when the None response type is requested | ||
// as defined in https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none | ||
type NoneResponseTypeHandler struct { | ||
Config interface { | ||
fosite.ScopeStrategyProvider | ||
fosite.AudienceStrategyProvider | ||
fosite.RedirectSecureCheckerProvider | ||
fosite.OmitRedirectScopeParamProvider | ||
} | ||
} | ||
|
||
func (c *NoneResponseTypeHandler) secureChecker(ctx context.Context) func(context.Context, *url.URL) bool { | ||
if c.Config.GetRedirectSecureChecker(ctx) == nil { | ||
return fosite.IsRedirectURISecure | ||
} | ||
return c.Config.GetRedirectSecureChecker(ctx) | ||
} | ||
|
||
func (c *NoneResponseTypeHandler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { | ||
if !ar.GetResponseTypes().ExactOne("none") { | ||
return nil | ||
} | ||
|
||
ar.SetDefaultResponseMode(fosite.ResponseModeQuery) | ||
|
||
if !c.secureChecker(ctx)(ctx, ar.GetRedirectURI()) { | ||
return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Redirect URL is using an insecure protocol, http is only allowed for hosts with suffix 'localhost', for example: http://myapp.localhost/.")) | ||
} | ||
|
||
client := ar.GetClient() | ||
for _, scope := range ar.GetRequestedScopes() { | ||
if !c.Config.GetScopeStrategy(ctx)(client.GetScopes(), scope) { | ||
return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) | ||
} | ||
} | ||
|
||
if err := c.Config.GetAudienceStrategy(ctx)(client.GetAudience(), ar.GetRequestedAudience()); err != nil { | ||
return err | ||
} | ||
|
||
resp.AddParameter("state", ar.GetState()) | ||
if !c.Config.GetOmitRedirectScopeParam(ctx) { | ||
resp.AddParameter("scope", strings.Join(ar.GetGrantedScopes(), " ")) | ||
} | ||
ar.SetResponseTypeHandled("none") | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package oauth2 | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ory/fosite" | ||
) | ||
|
||
func TestNone_HandleAuthorizeEndpointRequest(t *testing.T) { | ||
handler := NoneResponseTypeHandler{ | ||
Config: &fosite.Config{ | ||
ScopeStrategy: fosite.HierarchicScopeStrategy, | ||
AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, | ||
}, | ||
} | ||
for _, c := range []struct { | ||
handler NoneResponseTypeHandler | ||
areq *fosite.AuthorizeRequest | ||
description string | ||
expectErr error | ||
expect func(t *testing.T, areq *fosite.AuthorizeRequest, aresp *fosite.AuthorizeResponse) | ||
}{ | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{""}, | ||
Request: *fosite.NewRequest(), | ||
}, | ||
description: "should pass because not responsible for handling an empty response type", | ||
}, | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"foo"}, | ||
Request: *fosite.NewRequest(), | ||
}, | ||
description: "should pass because not responsible for handling an invalid response type", | ||
}, | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
Request: fosite.Request{ | ||
Client: &fosite.DefaultClient{ | ||
ResponseTypes: fosite.Arguments{"code", "none"}, | ||
RedirectURIs: []string{"http://asdf.com/cb"}, | ||
}, | ||
}, | ||
RedirectURI: parseUrl("http://asdf.com/cb"), | ||
}, | ||
description: "should fail because redirect uri is not https", | ||
expectErr: fosite.ErrInvalidRequest, | ||
}, | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
Request: fosite.Request{ | ||
Client: &fosite.DefaultClient{ | ||
ResponseTypes: fosite.Arguments{"code", "none"}, | ||
RedirectURIs: []string{"https://asdf.com/cb"}, | ||
Audience: []string{"https://www.ory.sh/api"}, | ||
}, | ||
RequestedAudience: []string{"https://www.ory.sh/not-api"}, | ||
}, | ||
RedirectURI: parseUrl("https://asdf.com/cb"), | ||
}, | ||
description: "should fail because audience doesn't match", | ||
expectErr: fosite.ErrInvalidRequest, | ||
}, | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
Request: fosite.Request{ | ||
Client: &fosite.DefaultClient{ | ||
ResponseTypes: fosite.Arguments{"code", "none"}, | ||
RedirectURIs: []string{"https://asdf.de/cb"}, | ||
Audience: []string{"https://www.ory.sh/api"}, | ||
}, | ||
RequestedAudience: []string{"https://www.ory.sh/api"}, | ||
GrantedScope: fosite.Arguments{"a", "b"}, | ||
Session: &fosite.DefaultSession{ | ||
ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().UTC().Add(time.Hour)}, | ||
}, | ||
RequestedAt: time.Now().UTC(), | ||
}, | ||
State: "superstate", | ||
RedirectURI: parseUrl("https://asdf.de/cb"), | ||
}, | ||
description: "should pass", | ||
expect: func(t *testing.T, areq *fosite.AuthorizeRequest, aresp *fosite.AuthorizeResponse) { | ||
assert.Equal(t, strings.Join(areq.GrantedScope, " "), aresp.GetParameters().Get("scope")) | ||
assert.Equal(t, areq.State, aresp.GetParameters().Get("state")) | ||
assert.Equal(t, fosite.ResponseModeQuery, areq.GetResponseMode()) | ||
}, | ||
}, | ||
{ | ||
handler: handler, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
Request: fosite.Request{ | ||
Client: &fosite.DefaultClient{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
RedirectURIs: []string{"https://asdf.de/cb"}, | ||
Audience: []string{"https://www.ory.sh/api"}, | ||
}, | ||
RequestedAudience: []string{"https://www.ory.sh/api"}, | ||
GrantedScope: fosite.Arguments{"a", "b"}, | ||
Session: &fosite.DefaultSession{ | ||
ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().UTC().Add(time.Hour)}, | ||
}, | ||
RequestedAt: time.Now().UTC(), | ||
}, | ||
State: "superstate", | ||
RedirectURI: parseUrl("https://asdf.de/cb"), | ||
}, | ||
description: "should pass with no response types other than none", | ||
expect: func(t *testing.T, areq *fosite.AuthorizeRequest, aresp *fosite.AuthorizeResponse) { | ||
assert.Equal(t, strings.Join(areq.GrantedScope, " "), aresp.GetParameters().Get("scope")) | ||
assert.Equal(t, areq.State, aresp.GetParameters().Get("state")) | ||
assert.Equal(t, fosite.ResponseModeQuery, areq.GetResponseMode()) | ||
}, | ||
}, | ||
{ | ||
handler: NoneResponseTypeHandler{ | ||
Config: &fosite.Config{ | ||
ScopeStrategy: fosite.HierarchicScopeStrategy, | ||
AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, | ||
OmitRedirectScopeParam: true, | ||
}, | ||
}, | ||
areq: &fosite.AuthorizeRequest{ | ||
ResponseTypes: fosite.Arguments{"none"}, | ||
Request: fosite.Request{ | ||
Client: &fosite.DefaultClient{ | ||
ResponseTypes: fosite.Arguments{"code", "none"}, | ||
RedirectURIs: []string{"https://asdf.de/cb"}, | ||
Audience: []string{"https://www.ory.sh/api"}, | ||
}, | ||
RequestedAudience: []string{"https://www.ory.sh/api"}, | ||
GrantedScope: fosite.Arguments{"a", "b"}, | ||
Session: &fosite.DefaultSession{ | ||
ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().UTC().Add(time.Hour)}, | ||
}, | ||
RequestedAt: time.Now().UTC(), | ||
}, | ||
State: "superstate", | ||
RedirectURI: parseUrl("https://asdf.de/cb"), | ||
}, | ||
description: "should pass but no scope in redirect uri", | ||
expect: func(t *testing.T, areq *fosite.AuthorizeRequest, aresp *fosite.AuthorizeResponse) { | ||
assert.Empty(t, aresp.GetParameters().Get("scope")) | ||
assert.Equal(t, areq.State, aresp.GetParameters().Get("state")) | ||
assert.Equal(t, fosite.ResponseModeQuery, areq.GetResponseMode()) | ||
}, | ||
}, | ||
} { | ||
t.Run("case="+c.description, func(t *testing.T) { | ||
aresp := fosite.NewAuthorizeResponse() | ||
err := c.handler.HandleAuthorizeEndpointRequest(context.Background(), c.areq, aresp) | ||
if c.expectErr != nil { | ||
require.EqualError(t, err, c.expectErr.Error()) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
|
||
if c.expect != nil { | ||
c.expect(t, c.areq, aresp) | ||
} | ||
}) | ||
} | ||
} |