...

Source file src/github.com/ory/fosite/handler/pkce/handler_test.go

Documentation: github.com/ory/fosite/handler/pkce

     1  /*
     2   * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
    17   * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
    18   * @license 	Apache-2.0
    19   *
    20   */
    21  
    22  package pkce
    23  
    24  import (
    25  	"context"
    26  	"crypto/sha256"
    27  	"encoding/base64"
    28  	"fmt"
    29  	"testing"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"github.com/ory/fosite"
    35  	"github.com/ory/fosite/handler/oauth2"
    36  	"github.com/ory/fosite/storage"
    37  )
    38  
    39  type mockCodeStrategy struct {
    40  	signature string
    41  }
    42  
    43  func (m *mockCodeStrategy) AuthorizeCodeSignature(token string) string {
    44  	return m.signature
    45  }
    46  
    47  func (m *mockCodeStrategy) GenerateAuthorizeCode(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) {
    48  	return "", "", nil
    49  }
    50  
    51  func (m *mockCodeStrategy) ValidateAuthorizeCode(ctx context.Context, requester fosite.Requester, token string) (err error) {
    52  	return nil
    53  }
    54  
    55  func TestPKCEHandleAuthorizeEndpointRequest(t *testing.T) {
    56  	h := &Handler{
    57  		Storage:               storage.NewMemoryStore(),
    58  		AuthorizeCodeStrategy: new(oauth2.HMACSHAStrategy),
    59  	}
    60  	w := fosite.NewAuthorizeResponse()
    61  	r := fosite.NewAuthorizeRequest()
    62  	c := &fosite.DefaultClient{}
    63  	r.Client = c
    64  
    65  	w.AddParameter("code", "foo")
    66  
    67  	r.Form.Add("code_challenge", "challenge")
    68  	r.Form.Add("code_challenge_method", "plain")
    69  
    70  	r.ResponseTypes = fosite.Arguments{}
    71  	require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    72  
    73  	r.ResponseTypes = fosite.Arguments{"code"}
    74  	require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    75  
    76  	r.ResponseTypes = fosite.Arguments{"code", "id_token"}
    77  	require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    78  
    79  	c.Public = true
    80  	h.EnablePlainChallengeMethod = true
    81  	require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    82  
    83  	c.Public = false
    84  	h.EnablePlainChallengeMethod = true
    85  	require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    86  
    87  	h.EnablePlainChallengeMethod = false
    88  	require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    89  
    90  	r.Form.Set("code_challenge_method", "S256")
    91  	r.Form.Set("code_challenge", "")
    92  	h.Force = true
    93  	require.Error(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    94  
    95  	r.Form.Set("code_challenge", "challenge")
    96  	require.NoError(t, h.HandleAuthorizeEndpointRequest(context.Background(), r, w))
    97  }
    98  
    99  func TestPKCEHandlerValidate(t *testing.T) {
   100  	s := storage.NewMemoryStore()
   101  	ms := &mockCodeStrategy{}
   102  	h := &Handler{
   103  		Storage: s, AuthorizeCodeStrategy: ms,
   104  	}
   105  	pc := &fosite.DefaultClient{Public: true}
   106  
   107  	s256verifier := "KGCt4m8AmjUvIR5ArTByrmehjtbxn1A49YpTZhsH8N7fhDr7LQayn9xx6mck"
   108  	hash := sha256.New()
   109  	hash.Write([]byte(s256verifier))
   110  	s256challenge := base64.RawURLEncoding.EncodeToString(hash.Sum([]byte{}))
   111  
   112  	for k, tc := range []struct {
   113  		d           string
   114  		grant       string
   115  		force       bool
   116  		enablePlain bool
   117  		challenge   string
   118  		method      string
   119  		verifier    string
   120  		code        string
   121  		expectErr   error
   122  		client      *fosite.DefaultClient
   123  	}{
   124  		{
   125  			d:         "fails because not auth code flow",
   126  			grant:     "not_authorization_code",
   127  			expectErr: fosite.ErrUnknownRequest,
   128  		},
   129  		{
   130  			d:           "passes with private client",
   131  			grant:       "authorization_code",
   132  			challenge:   "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   133  			verifier:    "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   134  			method:      "plain",
   135  			client:      &fosite.DefaultClient{Public: false},
   136  			enablePlain: true,
   137  			force:       true,
   138  			code:        "valid-code-1",
   139  		},
   140  		{
   141  			d:         "fails because invalid code",
   142  			grant:     "authorization_code",
   143  			expectErr: fosite.ErrInvalidGrant,
   144  			client:    pc,
   145  			code:      "invalid-code-2",
   146  		},
   147  		{
   148  			d:      "passes because auth code flow but pkce is not forced and no challenge given",
   149  			grant:  "authorization_code",
   150  			client: pc,
   151  			code:   "valid-code-3",
   152  		},
   153  		{
   154  			d:         "fails because auth code flow and pkce challenge given but plain is disabled",
   155  			grant:     "authorization_code",
   156  			challenge: "foo",
   157  			client:    pc,
   158  			expectErr: fosite.ErrInvalidRequest,
   159  			code:      "valid-code-4",
   160  		},
   161  		{
   162  			d:           "passes",
   163  			grant:       "authorization_code",
   164  			challenge:   "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   165  			verifier:    "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   166  			client:      pc,
   167  			enablePlain: true,
   168  			force:       true,
   169  			code:        "valid-code-5",
   170  		},
   171  		{
   172  			d:           "passes",
   173  			grant:       "authorization_code",
   174  			challenge:   "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   175  			verifier:    "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   176  			method:      "plain",
   177  			client:      pc,
   178  			enablePlain: true,
   179  			force:       true,
   180  			code:        "valid-code-6",
   181  		},
   182  		{
   183  			d:           "fails because challenge and verifier do not match",
   184  			grant:       "authorization_code",
   185  			challenge:   "not-foo",
   186  			verifier:    "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   187  			method:      "plain",
   188  			client:      pc,
   189  			enablePlain: true,
   190  			code:        "valid-code-7",
   191  			expectErr:   fosite.ErrInvalidGrant,
   192  		},
   193  		{
   194  			d:           "fails because challenge and verifier do not match",
   195  			grant:       "authorization_code",
   196  			challenge:   "not-foonot-foonot-foonot-foonot-foonot-foonot-foonot-foo",
   197  			verifier:    "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   198  			client:      pc,
   199  			enablePlain: true,
   200  			code:        "valid-code-8",
   201  			expectErr:   fosite.ErrInvalidGrant,
   202  		},
   203  		{
   204  			d:         "fails because verifier is too short",
   205  			grant:     "authorization_code",
   206  			challenge: "foo",
   207  			verifier:  "foo",
   208  			method:    "S256",
   209  			client:    pc,
   210  			force:     true,
   211  			code:      "valid-code-9a",
   212  			expectErr: fosite.ErrInvalidGrant,
   213  		},
   214  		{
   215  			d:         "fails because verifier is too long",
   216  			grant:     "authorization_code",
   217  			challenge: "foo",
   218  			verifier:  "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
   219  			method:    "S256",
   220  			client:    pc,
   221  			force:     true,
   222  			code:      "valid-code-10",
   223  			expectErr: fosite.ErrInvalidGrant,
   224  		},
   225  		{
   226  			d:         "fails because verifier is malformed",
   227  			grant:     "authorization_code",
   228  			challenge: "foo",
   229  			verifier:  `(!"/$%Z&$T()/)OUZI>$"&=/T(PUOI>"%/)TUOI&/(O/()RGTE>=/(%"/()="$/)(=()=/R/()=))`,
   230  			method:    "S256",
   231  			client:    pc,
   232  			force:     true,
   233  			code:      "valid-code-11",
   234  			expectErr: fosite.ErrInvalidGrant,
   235  		},
   236  		{
   237  			d:         "fails because challenge and verifier do not match",
   238  			grant:     "authorization_code",
   239  			challenge: "Zm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9v",
   240  			verifier:  "Zm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9vZm9v",
   241  			method:    "S256",
   242  			client:    pc,
   243  			force:     true,
   244  			code:      "valid-code-12",
   245  			expectErr: fosite.ErrInvalidGrant,
   246  		},
   247  		{
   248  			d:         "passes because challenge and verifier match",
   249  			grant:     "authorization_code",
   250  			challenge: s256challenge,
   251  			verifier:  s256verifier,
   252  			method:    "S256",
   253  			client:    pc,
   254  			force:     true,
   255  			code:      "valid-code-13",
   256  		},
   257  	} {
   258  		t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
   259  			h.EnablePlainChallengeMethod = tc.enablePlain
   260  			h.Force = tc.force
   261  			ms.signature = tc.code
   262  			ar := fosite.NewAuthorizeRequest()
   263  			ar.Form.Add("code_challenge", tc.challenge)
   264  			ar.Form.Add("code_challenge_method", tc.method)
   265  			require.NoError(t, s.CreatePKCERequestSession(nil, fmt.Sprintf("valid-code-%d", k), ar))
   266  
   267  			r := fosite.NewAccessRequest(nil)
   268  			r.Client = tc.client
   269  			r.GrantTypes = fosite.Arguments{tc.grant}
   270  			r.Form.Add("code_verifier", tc.verifier)
   271  			if tc.expectErr == nil {
   272  				require.NoError(t, h.HandleTokenEndpointRequest(context.Background(), r))
   273  			} else {
   274  				err := h.HandleTokenEndpointRequest(context.Background(), r)
   275  				require.EqualError(t, err, tc.expectErr.Error(), "%+v", err)
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestPKCEHandleTokenEndpointRequest(t *testing.T) {
   282  	for k, tc := range []struct {
   283  		d           string
   284  		force       bool
   285  		forcePublic bool
   286  		enablePlain bool
   287  		challenge   string
   288  		method      string
   289  		expectErr   bool
   290  		client      *fosite.DefaultClient
   291  	}{
   292  		{
   293  			d: "should pass because pkce is not enforced",
   294  		},
   295  		{
   296  			d:         "should fail because plain is not enabled and method is empty which defaults to plain",
   297  			expectErr: true,
   298  			force:     true,
   299  		},
   300  		{
   301  			d:           "should fail because force is enabled and no challenge was given",
   302  			force:       true,
   303  			enablePlain: true,
   304  			expectErr:   true,
   305  			method:      "S256",
   306  		},
   307  		{
   308  			d:           "should fail because forcePublic is enabled, the client is public, and no challenge was given",
   309  			forcePublic: true,
   310  			client:      &fosite.DefaultClient{Public: true},
   311  			expectErr:   true,
   312  			method:      "S256",
   313  		},
   314  		{
   315  			d:         "should fail because although force is enabled and a challenge was given, plain is disabled",
   316  			force:     true,
   317  			expectErr: true,
   318  			method:    "plain",
   319  			challenge: "challenge",
   320  		},
   321  		{
   322  			d:         "should fail because although force is enabled and a challenge was given, plain is disabled and method is empty",
   323  			force:     true,
   324  			expectErr: true,
   325  			challenge: "challenge",
   326  		},
   327  		{
   328  			d:         "should fail because invalid challenge method",
   329  			force:     true,
   330  			expectErr: true,
   331  			method:    "invalid",
   332  			challenge: "challenge",
   333  		},
   334  		{
   335  			d:         "should pass because force is enabled with challenge given and method is S256",
   336  			force:     true,
   337  			method:    "S256",
   338  			challenge: "challenge",
   339  		},
   340  		{
   341  			d:           "should pass because forcePublic is enabled with challenge given and method is S256",
   342  			forcePublic: true,
   343  			client:      &fosite.DefaultClient{Public: true},
   344  			method:      "S256",
   345  			challenge:   "challenge",
   346  		},
   347  	} {
   348  		t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
   349  			h := &Handler{
   350  				Force:                      tc.force,
   351  				ForceForPublicClients:      tc.forcePublic,
   352  				EnablePlainChallengeMethod: tc.enablePlain,
   353  			}
   354  
   355  			if tc.expectErr {
   356  				assert.Error(t, h.validate(tc.challenge, tc.method, tc.client))
   357  			} else {
   358  				assert.NoError(t, h.validate(tc.challenge, tc.method, tc.client))
   359  			}
   360  		})
   361  	}
   362  }
   363  

View as plain text