diff --git a/handler/oauth2/flow_authorize_code_token_test.go b/handler/oauth2/flow_authorize_code_token_test.go index e46b2cf1..e0646806 100644 --- a/handler/oauth2/flow_authorize_code_token_test.go +++ b/handler/oauth2/flow_authorize_code_token_test.go @@ -28,19 +28,25 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - var h GenericCodeTokenEndpointHandler - testCases := []struct { description string areq *fosite.AccessRequest - setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + authreq *fosite.AuthorizeRequest + setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest, config *fosite.Config) check func(t *testing.T, aresp *fosite.AccessResponse) expectErr error }{ { - description: "should fail because not responsible", + description: "should fail because not responsible for handling the request", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"123"}, + GrantTypes: fosite.Arguments{"implicit"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code"}, + }, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, }, expectErr: fosite.ErrUnknownRequest, }, @@ -57,7 +63,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + setup: func(t *testing.T, areq *fosite.AccessRequest, _ *fosite.AuthorizeRequest, _ *fosite.Config) { code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) areq.Form.Set("code", code) @@ -65,25 +71,37 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr: fosite.ErrServerError, }, { - description: "should pass with offline scope and refresh token", + description: "should pass with offline scope and refresh token grant type", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ + ID: "foo", GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + authreq: &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + RequestedScope: fosite.Arguments{"foo", "bar", "offline"}, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest, _ *fosite.Config) { code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form.Add("code", code) + areq.Form.Set("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -94,26 +112,38 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { - description: "should pass with refresh token always provided", + description: "should pass with refresh token grant type", areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ + ID: "foo", GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + authreq: &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + RequestedScope: fosite.Arguments{"foo", "bar"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest, config *fosite.Config) { config.RefreshTokenScopes = []string{} code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form.Add("code", code) + areq.Form.Set("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, areq)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -130,19 +160,31 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ + ID: "foo", GrantTypes: fosite.Arguments{"authorization_code"}, }, - GrantedScope: fosite.Arguments{"foo"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + authreq: &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + RequestedScope: fosite.Arguments{"foo", "bar"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest, _ *fosite.Config) { code, sig, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form.Add("code", code) + areq.Form.Set("code", code) - require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, areq)) + require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), sig, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -162,7 +204,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { AccessTokenLifespan: time.Minute, RefreshTokenScopes: []string{"offline"}, } - h = GenericCodeTokenEndpointHandler{ + h := GenericCodeTokenEndpointHandler{ AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, CodeHandler: &AuthorizeCodeHandler{ AuthorizeCodeStrategy: strategy, @@ -177,7 +219,7 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) { } if testCase.setup != nil { - testCase.setup(t, testCase.areq, config) + testCase.setup(t, testCase.areq, testCase.authreq, config) } aresp := fosite.NewAccessResponse() @@ -230,9 +272,18 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { expectErr error }{ { - description: "should fail because not responsible", + description: "should fail because not responsible for handling the request", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"12345678"}, + GrantTypes: fosite.Arguments{"implicit"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, }, expectErr: fosite.ErrUnknownRequest, }, @@ -241,7 +292,11 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{""}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, @@ -253,15 +308,19 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - token, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form = url.Values{"code": {token}} + areq.Form.Set("code", code) }, expectErr: fosite.ErrInvalidGrant, }, @@ -270,15 +329,27 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Form: url.Values{"code": {"foo.bar"}}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Form: url.Values{ + "code": {"foo.bar"}, + "redirect_uri": []string{"request-redir"}, + }, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo"}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &fosite.DefaultSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.AuthorizeCode: time.Now().Add(-time.Hour).UTC(), @@ -288,9 +359,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form = url.Values{"code": {token}} + areq.Form.Set("code", code) require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, @@ -301,14 +372,24 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "bar"}, + Client: &fosite.DefaultClient{ + ID: "bar", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &fosite.DefaultSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.AuthorizeCode: time.Now().Add(time.Hour).UTC(), @@ -317,9 +398,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form = url.Values{"code": {token}} + areq.Form.Set("code", code) require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, @@ -330,15 +411,24 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, - Form: url.Values{"redirect_uri": []string{"request-redir"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &fosite.DefaultSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.AuthorizeCode: time.Now().Add(time.Hour).UTC(), @@ -347,9 +437,9 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form = url.Values{"code": {token}} + areq.Form.Set("code", code) require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, @@ -360,7 +450,10 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, Form: url.Values{"redirect_uri": []string{"request-redir"}}, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), @@ -368,16 +461,22 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { - token, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) + areq.Form.Set("code", code) - areq.Form = url.Values{"code": {token}} require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) }, }, @@ -386,23 +485,32 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) { areq: &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: fosite.Arguments{"authorization_code"}}, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{"authorization_code"}, + }, Session: &fosite.DefaultSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.AuthorizeRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"authorization_code"}}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + Form: url.Values{"redirect_uri": []string{"request-redir"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, }, setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.AuthorizeRequest) { code, signature, err := strategy.GenerateAuthorizeCode(context.Background(), nil) require.NoError(t, err) - areq.Form.Add("code", code) + areq.Form.Set("code", code) require.NoError(t, store.CreateAuthorizeCodeSession(context.Background(), signature, authreq)) require.NoError(t, store.InvalidateAuthorizeCodeSession(context.Background(), signature)) @@ -439,23 +547,37 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { var mockCoreStore *internal.MockCoreStorage var mockAuthorizeStore *internal.MockAuthorizeCodeStorage strategy := hmacshaStrategy - request := &fosite.AccessRequest{ + + authreq := &fosite.AuthorizeRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"authorization_code"}, + }, + RequestedScope: fosite.Arguments{"foo", "offline"}, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), + }, + } + + areq := &fosite.AccessRequest{ GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ Client: &fosite.DefaultClient{ GrantTypes: fosite.Arguments{"authorization_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"offline"}, - Session: &fosite.DefaultSession{}, - RequestedAt: time.Now().UTC(), + Session: &fosite.DefaultSession{}, + RequestedAt: time.Now().UTC(), }, } - token, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) - require.NoError(t, err) - request.Form = url.Values{"code": {token}} - response := fosite.NewAccessResponse() + aresp := fosite.NewAccessResponse() propagatedContext := context.Background() + code, _, err := strategy.GenerateAuthorizeCode(context.Background(), nil) + require.NoError(t, err) + areq.Form = url.Values{"code": {code}} + // some storage implementation that has support for transactions, notice the embedded type `storage.Transactional` type transactionalStore struct { storage.Transactional @@ -478,7 +600,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -512,7 +634,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -537,7 +659,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -567,7 +689,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -582,7 +704,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -607,7 +729,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockAuthorizeStore. EXPECT(). GetAuthorizeCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -658,7 +780,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, AuthorizeCodeLifespan: time.Minute, } - handler := GenericCodeTokenEndpointHandler{ + h := GenericCodeTokenEndpointHandler{ AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{}, CodeHandler: &AuthorizeCodeHandler{ AuthorizeCodeStrategy: strategy, @@ -678,7 +800,7 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { Config: config, } - if err := handler.PopulateTokenEndpointResponse(propagatedContext, request, response); testCase.expectError != nil { + if err := h.PopulateTokenEndpointResponse(propagatedContext, areq, aresp); testCase.expectError != nil { assert.EqualError(t, err, testCase.expectError.Error()) } }) diff --git a/handler/oauth2/flow_generic_code_token.go b/handler/oauth2/flow_generic_code_token.go index e469e558..031ec84e 100644 --- a/handler/oauth2/flow_generic_code_token.go +++ b/handler/oauth2/flow_generic_code_token.go @@ -90,7 +90,7 @@ func (c *GenericCodeTokenEndpointHandler) PopulateTokenEndpointResponse(ctx cont return errorsx.WithStack(err) } - for _, scope := range ar.GetRequestedScopes() { + for _, scope := range ar.GetGrantedScopes() { requester.GrantScope(scope) } @@ -105,7 +105,7 @@ func (c *GenericCodeTokenEndpointHandler) PopulateTokenEndpointResponse(ctx cont } var refreshToken, refreshTokenSignature string - if c.canIssueRefreshToken(ctx, ar) { + if c.canIssueRefreshToken(ctx, requester) { refreshToken, refreshTokenSignature, err = c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) if err != nil { return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) @@ -229,11 +229,14 @@ func (c *GenericCodeTokenEndpointHandler) CanHandleTokenEndpointRequest(ctx cont } func (c *GenericCodeTokenEndpointHandler) canIssueRefreshToken(ctx context.Context, requester fosite.Requester) bool { - scope := c.Config.GetRefreshTokenScopes(ctx) - if len(scope) > 0 && !requester.GetGrantedScopes().HasOneOf(scope...) { + scopes := c.Config.GetRefreshTokenScopes(ctx) + + // Require one of the refresh token scopes, if set. + if len(scopes) > 0 && !requester.GetGrantedScopes().HasOneOf(scopes...) { return false } + // Do not issue a refresh token to clients that cannot use the refresh token grant type. if !requester.GetClient().GetGrantTypes().Has("refresh_token") { return false } diff --git a/handler/openid/flow_device_auth.go b/handler/openid/flow_device_auth.go index 3a54b494..efeb2115 100644 --- a/handler/openid/flow_device_auth.go +++ b/handler/openid/flow_device_auth.go @@ -25,7 +25,7 @@ type OpenIDConnectDeviceHandler struct { } func (c *OpenIDConnectDeviceHandler) HandleDeviceEndpointRequest(ctx context.Context, dar fosite.DeviceRequester, resp fosite.DeviceResponder) error { - if !(dar.GetGrantedScopes().Has("openid")) { + if !(dar.GetRequestedScopes().Has("openid")) { return nil } @@ -33,7 +33,7 @@ func (c *OpenIDConnectDeviceHandler) HandleDeviceEndpointRequest(ctx context.Con return nil } - if len(resp.GetDeviceCode()) == 0 { + if resp.GetDeviceCode() == "" { return errorsx.WithStack(fosite.ErrMisconfiguration.WithDebug("The device code has not been issued yet, indicating a broken code configuration.")) } diff --git a/handler/openid/flow_device_auth_test.go b/handler/openid/flow_device_auth_test.go index a84e1e44..a345660f 100644 --- a/handler/openid/flow_device_auth_test.go +++ b/handler/openid/flow_device_auth_test.go @@ -64,7 +64,7 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { client := &fosite.DefaultClient{ ID: "foo", - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, } testCases := []struct { @@ -78,7 +78,7 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { description: "should ignore because scope openid is not set", authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - GrantedScope: fosite.Arguments{"email"}, + RequestedScope: fosite.Arguments{"email"}, }, }, }, @@ -86,9 +86,9 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { description: "should ignore because client grant type is invalid", authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - GrantedScope: fosite.Arguments{"openid", "email"}, + RequestedScope: fosite.Arguments{"openid", "email"}, Client: &fosite.DefaultClient{ - GrantTypes: []string{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: []string{"authorization_code"}, }, }, }, @@ -97,8 +97,8 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { description: "should fail because device code is not issued", authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - GrantedScope: fosite.Arguments{"openid", "email"}, - Client: client, + RequestedScope: fosite.Arguments{"openid", "email"}, + Client: client, }, }, authresp: &fosite.DeviceResponse{}, @@ -108,9 +108,9 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { description: "should fail because cannot create session", authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - GrantedScope: fosite.Arguments{"openid", "email"}, - Client: client, - Session: session, + RequestedScope: fosite.Arguments{"openid", "email"}, + Client: client, + Session: session, }, }, authresp: &fosite.DeviceResponse{ @@ -128,9 +128,9 @@ func TestDeviceAuth_HandleDeviceEndpointRequest(t *testing.T) { description: "should pass", authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - GrantedScope: fosite.Arguments{"openid", "email"}, - Client: client, - Session: session, + RequestedScope: fosite.Arguments{"openid", "email"}, + Client: client, + Session: session, }, }, authresp: &fosite.DeviceResponse{ diff --git a/handler/openid/flow_device_token.go b/handler/openid/flow_device_token.go index 5456b0b4..c0e2660e 100644 --- a/handler/openid/flow_device_token.go +++ b/handler/openid/flow_device_token.go @@ -21,6 +21,10 @@ func (c *OpenIDConnectDeviceHandler) PopulateTokenEndpointResponse(ctx context.C return errorsx.WithStack(fosite.ErrUnknownRequest) } + if !requester.GetClient().GetGrantTypes().Has(string(fosite.GrantTypeDeviceCode)) { + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"urn:ietf:params:oauth:grant-type:device_code\".")) + } + deviceCode := requester.GetRequestForm().Get("device_code") signature, err := c.DeviceCodeStrategy.DeviceCodeSignature(ctx, deviceCode) ar, err := c.OpenIDConnectRequestStorage.GetOpenIDConnectSession(ctx, signature, requester) @@ -35,10 +39,6 @@ func (c *OpenIDConnectDeviceHandler) PopulateTokenEndpointResponse(ctx context.C return errorsx.WithStack(fosite.ErrMisconfiguration.WithDebug("An OpenID Connect session was found but the openid scope is missing, probably due to a broken code configuration.")) } - if !requester.GetClient().GetGrantTypes().Has(string(fosite.GrantTypeDeviceCode)) { - return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"urn:ietf:params:oauth:grant-type:device_code\".")) - } - session, ok := ar.GetSession().(Session) if !ok { return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) diff --git a/handler/openid/flow_device_token_test.go b/handler/openid/flow_device_token_test.go index 54f9686e..03a51cd6 100644 --- a/handler/openid/flow_device_token_test.go +++ b/handler/openid/flow_device_token_test.go @@ -81,7 +81,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { client := &fosite.DefaultClient{ ID: "foo", - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, } testCases := []struct { @@ -95,8 +95,9 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because the grant type is invalid", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, + GrantTypes: fosite.Arguments{"authorization_code"}, Request: fosite.Request{ + Client: client, Form: url.Values{"device_code": []string{"device_code"}}, Session: session, }, @@ -107,8 +108,9 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because session not found", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ + Client: client, Form: url.Values{"device_code": []string{"device_code"}}, Session: session, }, @@ -122,8 +124,9 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because session lookup fails", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ + Client: client, Form: url.Values{"device_code": []string{"device_code"}}, Session: session, }, @@ -136,8 +139,9 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because auth request grant scope is invalid", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ + Client: client, Form: url.Values{"device_code": []string{"device_code"}}, Session: session, }, @@ -145,6 +149,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { setup: func(areq *fosite.AccessRequest) { authreq := &fosite.DeviceRequest{ Request: fosite.Request{ + Client: client, GrantedScope: fosite.Arguments{"email"}, Session: session, }, @@ -153,33 +158,10 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { }, expectErr: fosite.ErrMisconfiguration, }, - { - description: "should fail because auth request's client grant type is invalid", - areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, - Request: fosite.Request{ - Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)}, - }, - Form: url.Values{"device_code": []string{"device_code"}}, - Session: session, - }, - }, - setup: func(areq *fosite.AccessRequest) { - authreq := &fosite.DeviceRequest{ - Request: fosite.Request{ - GrantedScope: fosite.Arguments{"openid", "email"}, - Session: session, - }, - } - store.EXPECT().GetOpenIDConnectSession(gomock.Any(), gomock.Any(), areq).Return(authreq, nil) - }, - expectErr: fosite.ErrUnauthorizedClient, - }, { description: "should fail because auth request is missing session", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Client: client, Form: url.Values{"device_code": []string{"device_code"}}, @@ -189,6 +171,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { setup: func(areq *fosite.AccessRequest) { authreq := &fosite.DeviceRequest{ Request: fosite.Request{ + Client: client, GrantedScope: fosite.Arguments{"openid", "email"}, }, } @@ -199,7 +182,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should fail because auth request session is missing subject claims", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Client: client, Form: url.Values{"device_code": []string{"device_code"}}, @@ -209,6 +192,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { setup: func(areq *fosite.AccessRequest) { authreq := &fosite.DeviceRequest{ Request: fosite.Request{ + Client: client, GrantedScope: fosite.Arguments{"openid", "email"}, Session: NewDefaultSession(), }, @@ -220,7 +204,7 @@ func TestDeviceToken_PopulateTokenEndpointResponse(t *testing.T) { { description: "should pass", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Client: client, Form: url.Values{"device_code": []string{"device_code"}}, diff --git a/handler/rfc8628/token_handler_test.go b/handler/rfc8628/token_handler_test.go index f3047514..dbec5fce 100644 --- a/handler/rfc8628/token_handler_test.go +++ b/handler/rfc8628/token_handler_test.go @@ -79,18 +79,28 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { expectErr error }{ { - description: "should fail because not responsible", + description: "should fail because not responsible for handling the request", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"12345678"}, + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), + }, }, expectErr: fosite.ErrUnknownRequest, }, { description: "should fail because client is not granted the correct grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{""}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{""}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, @@ -100,14 +110,17 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because device code could not be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest) { + setup: func(t *testing.T, areq *fosite.AccessRequest, _ *fosite.DeviceRequest) { deviceCode, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form = url.Values{"device_code": {deviceCode}} @@ -117,17 +130,25 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because user has not completed the browser flow", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Form: url.Values{}, - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Form: url.Values{}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), @@ -149,12 +170,12 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because device code has expired", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ ID: "foo", - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, }, GrantedScope: fosite.Arguments{"foo", "offline"}, Session: &DefaultDeviceFlowSession{}, @@ -163,7 +184,9 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(-time.Hour).UTC(), @@ -185,16 +208,21 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should fail because client mismatch", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "bar"}, + Client: &fosite.DefaultClient{ID: "bar"}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ ExpiresAt: map[fosite.TokenType]time.Time{ fosite.DeviceCode: time.Now().Add(time.Hour).UTC(), @@ -215,16 +243,24 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { { description: "should pass", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, authreq: &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ + ID: "foo", + GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, @@ -264,7 +300,7 @@ func TestDeviceUserCode_HandleTokenEndpointRequest(t *testing.T) { } } -func TestDeviceUserCode_HandleTokenEndpointRequest_Ratelimitting(t *testing.T) { +func TestDeviceUserCode_HandleTokenEndpointRequest_RateLimiting(t *testing.T) { for k, strategy := range map[string]struct { oauth2.CoreStrategy RFC8628CodeStrategy @@ -293,21 +329,22 @@ func TestDeviceUserCode_HandleTokenEndpointRequest_Ratelimitting(t *testing.T) { }, } areq := &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ ID: "foo", - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, }, - GrantedScope: fosite.Arguments{"foo", "offline"}, - Session: &DefaultDeviceFlowSession{}, - RequestedAt: time.Now().UTC(), + Session: &DefaultDeviceFlowSession{}, + RequestedAt: time.Now().UTC(), }, } authreq := &fosite.DeviceRequest{ Request: fosite.Request{ - Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{string(fosite.GrantTypeDeviceCode)}}, + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo"}, + GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, @@ -341,36 +378,46 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { t.Run("strategy="+k, func(t *testing.T) { store := storage.NewMemoryStore() - var h oauth2.GenericCodeTokenEndpointHandler - testCases := []struct { - areq *fosite.AccessRequest description string - setup func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) + areq *fosite.AccessRequest + authreq *fosite.DeviceRequest + setup func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest, config *fosite.Config) check func(t *testing.T, aresp *fosite.AccessResponse) expectErr error }{ { - description: "should fail because not responsible", + description: "should fail because not responsible for handling the request", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{"123"}, + GrantTypes: fosite.Arguments{"authorization_code"}, + Request: fosite.Request{ + Client: &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, }, expectErr: fosite.ErrUnknownRequest, }, { description: "should fail because device code cannot be retrieved", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, + }, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, }, - Session: &DefaultDeviceFlowSession{}, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + setup: func(t *testing.T, areq *fosite.AccessRequest, _ *fosite.DeviceRequest, _ *fosite.Config) { code, _, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Set("device_code", code) @@ -378,27 +425,37 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { expectErr: fosite.ErrServerError, }, { - description: "should pass with offline scope and refresh token", + description: "should pass with offline scope and refresh token grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"foo", "offline"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo", "bar", "offline"}, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest, _ *fosite.Config) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -409,28 +466,38 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { }, }, { - description: "should pass with refresh token always provided", + description: "should pass with refresh token grant type", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo", "bar"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest, config *fosite.Config) { config.RefreshTokenScopes = []string{} code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -443,25 +510,35 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { { description: "pass and response should not have refresh token", areq: &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Form: url.Values{}, Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, }, - GrantedScope: fosite.Arguments{"foo"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, }, - setup: func(t *testing.T, areq *fosite.AccessRequest, config *fosite.Config) { + authreq: &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo", "bar"}, + GrantedScope: fosite.Arguments{"foo"}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, + }, + setup: func(t *testing.T, areq *fosite.AccessRequest, authreq *fosite.DeviceRequest, config *fosite.Config) { code, signature, err := strategy.GenerateDeviceCode(context.TODO()) require.NoError(t, err) areq.Form.Add("device_code", code) - require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, areq)) + require.NoError(t, store.CreateDeviceCodeSession(context.TODO(), signature, authreq)) }, check: func(t *testing.T, aresp *fosite.AccessResponse) { assert.NotEmpty(t, aresp.AccessToken) @@ -481,7 +558,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { AccessTokenLifespan: time.Minute, RefreshTokenScopes: []string{"offline"}, } - h = oauth2.GenericCodeTokenEndpointHandler{ + h := oauth2.GenericCodeTokenEndpointHandler{ AccessRequestValidator: &DeviceAccessRequestValidator{}, CodeHandler: &DeviceCodeHandler{ DeviceRateLimitStrategy: strategy, @@ -498,7 +575,7 @@ func TestDeviceUserCode_PopulateTokenEndpointResponse(t *testing.T) { } if testCase.setup != nil { - testCase.setup(t, testCase.areq, config) + testCase.setup(t, testCase.areq, testCase.authreq, config) } aresp := fosite.NewAccessResponse() @@ -526,25 +603,38 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { var mockDeviceRateLimitStrategy *internal.MockDeviceRateLimitStrategy strategy := hmacshaStrategy deviceStrategy := RFC8628HMACSHAStrategy - request := &fosite.AccessRequest{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode)}, + + authreq := &fosite.DeviceRequest{ + Request: fosite.Request{ + Client: &fosite.DefaultClient{ID: "foo", GrantTypes: []string{"urn:ietf:params:oauth:grant-type:device_code"}}, + RequestedScope: fosite.Arguments{"foo", "offline"}, + GrantedScope: fosite.Arguments{"foo", "offline"}, + Session: &DefaultDeviceFlowSession{ + BrowserFlowCompleted: true, + }, + RequestedAt: time.Now().UTC(), + }, + } + + areq := &fosite.AccessRequest{ + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code"}, Request: fosite.Request{ Client: &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{string(fosite.GrantTypeDeviceCode), string(fosite.GrantTypeRefreshToken)}, + GrantTypes: fosite.Arguments{"urn:ietf:params:oauth:grant-type:device_code", "refresh_token"}, }, - GrantedScope: fosite.Arguments{"offline"}, Session: &DefaultDeviceFlowSession{ BrowserFlowCompleted: true, }, RequestedAt: time.Now().UTC(), }, } - token, _, err := deviceStrategy.GenerateDeviceCode(context.Background()) - require.NoError(t, err) - request.Form = url.Values{"device_code": {token}} - response := fosite.NewAccessResponse() + aresp := fosite.NewAccessResponse() propagatedContext := context.Background() + code, _, err := deviceStrategy.GenerateDeviceCode(context.Background()) + require.NoError(t, err) + areq.Form = url.Values{"device_code": {code}} + // some storage implementation that has support for transactions, notice the embedded type `storage.Transactional` type coreTransactionalStore struct { storage.Transactional @@ -567,7 +657,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -601,7 +691,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -626,7 +716,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -656,7 +746,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -671,7 +761,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -696,7 +786,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceCodeStore. EXPECT(). GetDeviceCodeSession(gomock.Any(), gomock.Any(), gomock.Any()). - Return(request, nil). + Return(authreq, nil). Times(1) mockTransactional. EXPECT(). @@ -743,7 +833,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { mockDeviceRateLimitStrategy = internal.NewMockDeviceRateLimitStrategy(ctrl) testCase.setup() - handler := oauth2.GenericCodeTokenEndpointHandler{ + h := oauth2.GenericCodeTokenEndpointHandler{ AccessRequestValidator: &DeviceAccessRequestValidator{}, CodeHandler: &DeviceCodeHandler{ DeviceRateLimitStrategy: mockDeviceRateLimitStrategy, @@ -768,7 +858,7 @@ func TestDeviceUserCodeTransactional_HandleTokenEndpointRequest(t *testing.T) { }, } - if err = handler.PopulateTokenEndpointResponse(propagatedContext, request, response); testCase.expectError != nil { + if err = h.PopulateTokenEndpointResponse(propagatedContext, areq, aresp); testCase.expectError != nil { assert.EqualError(t, err, testCase.expectError.Error()) } }) diff --git a/integration/authorize_device_grant_request_test.go b/integration/authorize_device_grant_request_test.go index 170c906f..9eb2460a 100644 --- a/integration/authorize_device_grant_request_test.go +++ b/integration/authorize_device_grant_request_test.go @@ -53,14 +53,14 @@ func runDeviceFlowTest(t *testing.T) { { description: "should fail with invalid_grant", setup: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeAuthorizationCode)} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} }, err: true, check: func(t *testing.T, token *goauth.DeviceAuthResponse, err error) { assert.ErrorContains(t, err, "invalid_grant") }, cleanUp: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeDeviceCode)} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} }, }, { @@ -152,7 +152,6 @@ func runDeviceFlowAccessTokenTest(t *testing.T) { description string setup func() params []goauth.AuthCodeOption - err bool check func(t *testing.T, token *goauth.Token, err error) cleanUp func() }{ @@ -161,7 +160,6 @@ func runDeviceFlowAccessTokenTest(t *testing.T) { setup: func() { }, params: []goauth.AuthCodeOption{goauth.SetAuthURLParam("grant_type", "invalid_grant_type")}, - err: true, check: func(t *testing.T, token *goauth.Token, err error) { assert.ErrorContains(t, err, "invalid_request") }, @@ -169,22 +167,20 @@ func runDeviceFlowAccessTokenTest(t *testing.T) { { description: "should fail with unauthorized client", setup: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeAuthorizationCode)} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"authorization_code"} }, params: []goauth.AuthCodeOption{}, - err: true, check: func(t *testing.T, token *goauth.Token, err error) { assert.ErrorContains(t, err, "unauthorized_client") }, cleanUp: func() { - fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{string(fosite.GrantTypeDeviceCode)} + fositeStore.Clients["device-client"].(*fosite.DefaultClient).GrantTypes = []string{"urn:ietf:params:oauth:grant-type:device_code"} }, }, { description: "should fail with invalid device code", setup: func() {}, params: []goauth.AuthCodeOption{goauth.SetAuthURLParam("device_code", "invalid_device_code")}, - err: true, check: func(t *testing.T, token *goauth.Token, err error) { assert.ErrorContains(t, err, "invalid_grant") }, @@ -194,7 +190,6 @@ func runDeviceFlowAccessTokenTest(t *testing.T) { setup: func() { oauthClient.ClientID = "invalid_client_id" }, - err: true, check: func(t *testing.T, token *goauth.Token, err error) { assert.ErrorContains(t, err, "invalid_client") }, @@ -204,17 +199,18 @@ func runDeviceFlowAccessTokenTest(t *testing.T) { }, { description: "should pass", - setup: func() {}, - err: false, + check: func(t *testing.T, token *goauth.Token, err error) { + assert.Equal(t, "bearer", token.TokenType) + assert.NotEmpty(t, token.AccessToken) + }, }, } { t.Run(fmt.Sprintf("case=%d description=%s", k, c.description), func(t *testing.T) { - c.setup() + if c.setup != nil { + c.setup() + } token, err := oauthClient.DeviceAccessToken(context.Background(), resp, c.params...) - if !c.err { - assert.NotEmpty(t, token.AccessToken) - } if c.check != nil { c.check(t, token, err)