...

Source file src/github.com/go-openapi/runtime/security/bearer_auth_test.go

Documentation: github.com/go-openapi/runtime/security

     1  package security
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/go-openapi/errors"
    14  	"github.com/go-openapi/runtime"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  const (
    20  	owners       = "owners_auth"
    21  	validToken   = "token123"
    22  	invalidToken = "token124"
    23  	principal    = "admin"
    24  	authPath     = "/blah"
    25  	invalidParam = "access_toke"
    26  )
    27  
    28  type authExpectation uint8
    29  
    30  const (
    31  	expectIsAuthorized authExpectation = iota
    32  	expectInvalidAuthorization
    33  	expectNoAuthorization
    34  )
    35  
    36  func TestBearerAuth(t *testing.T) {
    37  	bearerAuth := ScopedTokenAuthentication(func(token string, _ []string) (interface{}, error) {
    38  		if token == validToken {
    39  			return principal, nil
    40  		}
    41  		return nil, errors.Unauthenticated("bearer")
    42  	})
    43  	ba := BearerAuth(owners, bearerAuth)
    44  	ctx := context.Background()
    45  
    46  	t.Run("with valid bearer auth", func(t *testing.T) {
    47  		t.Run("token in query param",
    48  			testAuthenticateBearerInQuery(ctx, ba, "", validToken, expectIsAuthorized),
    49  		)
    50  		t.Run("token in header",
    51  			testAuthenticateBearerInHeader(ctx, ba, "", validToken, expectIsAuthorized),
    52  		)
    53  		t.Run("token in urlencoded form",
    54  			testAuthenticateBearerInForm(ctx, ba, "", validToken, expectIsAuthorized),
    55  		)
    56  		t.Run("token in multipart form",
    57  			testAuthenticateBearerInMultipartForm(ctx, ba, "", validToken, expectIsAuthorized),
    58  		)
    59  	})
    60  
    61  	t.Run("with invalid token", func(t *testing.T) {
    62  		t.Run("token in query param",
    63  			testAuthenticateBearerInQuery(ctx, ba, "", invalidToken, expectInvalidAuthorization),
    64  		)
    65  		t.Run("token in header",
    66  			testAuthenticateBearerInHeader(ctx, ba, "", invalidToken, expectInvalidAuthorization),
    67  		)
    68  		t.Run("token in urlencoded form",
    69  			testAuthenticateBearerInForm(ctx, ba, "", invalidToken, expectInvalidAuthorization),
    70  		)
    71  		t.Run("token in multipart form",
    72  			testAuthenticateBearerInMultipartForm(ctx, ba, "", invalidToken, expectInvalidAuthorization),
    73  		)
    74  	})
    75  
    76  	t.Run("with missing auth", func(t *testing.T) {
    77  		t.Run("token in query param",
    78  			testAuthenticateBearerInQuery(ctx, ba, invalidParam, validToken, expectNoAuthorization),
    79  		)
    80  		t.Run("token in header",
    81  			testAuthenticateBearerInHeader(ctx, ba, "Beare", validToken, expectNoAuthorization),
    82  		)
    83  		t.Run("token in urlencoded form",
    84  			testAuthenticateBearerInForm(ctx, ba, invalidParam, validToken, expectNoAuthorization),
    85  		)
    86  		t.Run("token in multipart form",
    87  			testAuthenticateBearerInMultipartForm(ctx, ba, invalidParam, validToken, expectNoAuthorization),
    88  		)
    89  	})
    90  }
    91  
    92  func TestBearerAuthCtx(t *testing.T) {
    93  	bearerAuthCtx := ScopedTokenAuthenticationCtx(func(ctx context.Context, token string, _ []string) (context.Context, interface{}, error) {
    94  		if token == validToken {
    95  			return context.WithValue(ctx, extra, extraWisdom), principal, nil
    96  		}
    97  		return context.WithValue(ctx, reason, expReason), nil, errors.Unauthenticated("bearer")
    98  	})
    99  	ba := BearerAuthCtx(owners, bearerAuthCtx)
   100  	ctx := context.WithValue(context.Background(), original, wisdom)
   101  
   102  	assertContextOK := func(requestContext context.Context, t *testing.T) {
   103  		// when authorized, we have an "extra" key in context
   104  		assert.Equal(t, wisdom, requestContext.Value(original))
   105  		assert.Equal(t, extraWisdom, requestContext.Value(extra))
   106  		assert.Nil(t, requestContext.Value(reason))
   107  	}
   108  
   109  	assertContextKO := func(requestContext context.Context, t *testing.T) {
   110  		// when not authorized, we have a "reason" key in context
   111  		assert.Equal(t, wisdom, requestContext.Value(original))
   112  		assert.Nil(t, requestContext.Value(extra))
   113  		assert.Equal(t, expReason, requestContext.Value(reason))
   114  	}
   115  
   116  	assertContextNone := func(requestContext context.Context, t *testing.T) {
   117  		// when missing authorization, we only have the original context
   118  		assert.Equal(t, wisdom, requestContext.Value(original))
   119  		assert.Nil(t, requestContext.Value(extra))
   120  		assert.Nil(t, requestContext.Value(reason))
   121  	}
   122  
   123  	t.Run("with valid bearer auth", func(t *testing.T) {
   124  		t.Run("token in query param",
   125  			testAuthenticateBearerInQuery(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
   126  		)
   127  		t.Run("token in header",
   128  			testAuthenticateBearerInHeader(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
   129  		)
   130  		t.Run("token in urlencoded form",
   131  			testAuthenticateBearerInForm(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
   132  		)
   133  		t.Run("token in multipart form",
   134  			testAuthenticateBearerInMultipartForm(ctx, ba, "", validToken, expectIsAuthorized, assertContextOK),
   135  		)
   136  	})
   137  
   138  	t.Run("with invalid token", func(t *testing.T) {
   139  		t.Run("token in query param",
   140  			testAuthenticateBearerInQuery(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
   141  		)
   142  		t.Run("token in header",
   143  			testAuthenticateBearerInHeader(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
   144  		)
   145  		t.Run("token in urlencoded form",
   146  			testAuthenticateBearerInForm(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
   147  		)
   148  		t.Run("token in multipart form",
   149  			testAuthenticateBearerInMultipartForm(ctx, ba, "", invalidToken, expectInvalidAuthorization, assertContextKO),
   150  		)
   151  	})
   152  
   153  	t.Run("with missing auth", func(t *testing.T) {
   154  		t.Run("token in query param",
   155  			testAuthenticateBearerInQuery(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
   156  		)
   157  		t.Run("token in header",
   158  			testAuthenticateBearerInHeader(ctx, ba, "Beare", validToken, expectNoAuthorization, assertContextNone),
   159  		)
   160  		t.Run("token in urlencoded form",
   161  			testAuthenticateBearerInForm(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
   162  		)
   163  		t.Run("token in multipart form",
   164  			testAuthenticateBearerInMultipartForm(ctx, ba, invalidParam, validToken, expectNoAuthorization, assertContextNone),
   165  		)
   166  	})
   167  }
   168  
   169  func testIsAuthorized(_ context.Context, req *http.Request, authorizer runtime.Authenticator, expectAuthorized authExpectation, extraAsserters ...func(context.Context, *testing.T)) func(*testing.T) {
   170  	return func(t *testing.T) {
   171  		hasToken, usr, err := authorizer.Authenticate(&ScopedAuthRequest{Request: req})
   172  		switch expectAuthorized {
   173  
   174  		case expectIsAuthorized:
   175  			require.NoError(t, err)
   176  			assert.True(t, hasToken)
   177  			assert.Equal(t, principal, usr)
   178  			assert.Equal(t, owners, OAuth2SchemeName(req))
   179  
   180  		case expectInvalidAuthorization:
   181  			require.Error(t, err)
   182  			require.ErrorContains(t, err, "unauthenticated")
   183  			assert.True(t, hasToken)
   184  			assert.Nil(t, usr)
   185  			assert.Equal(t, owners, OAuth2SchemeName(req))
   186  
   187  		case expectNoAuthorization:
   188  			require.NoError(t, err)
   189  			assert.False(t, hasToken)
   190  			assert.Nil(t, usr)
   191  			assert.Empty(t, OAuth2SchemeName(req))
   192  		}
   193  
   194  		for _, contextAsserter := range extraAsserters {
   195  			contextAsserter(req.Context(), t)
   196  		}
   197  	}
   198  }
   199  
   200  func shouldAuthorizeOrNot(expectAuthorized authExpectation) string {
   201  	if expectAuthorized == expectIsAuthorized {
   202  		return "should authorize"
   203  	}
   204  
   205  	return "should not authorize"
   206  }
   207  
   208  func testAuthenticateBearerInQuery(
   209  	// build a request with the token as a query parameter, then check against the authorizer
   210  	//
   211  	// the request context after authorization may be checked with the extraAsserters.
   212  	ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
   213  	extraAsserters ...func(context.Context, *testing.T),
   214  ) func(*testing.T) {
   215  	if parameter == "" {
   216  		parameter = accessTokenParam
   217  	}
   218  
   219  	return func(t *testing.T) {
   220  		req, err := http.NewRequestWithContext(
   221  			ctx, http.MethodGet,
   222  			fmt.Sprintf("%s?%s=%s", authPath, parameter, token),
   223  			nil,
   224  		)
   225  		require.NoError(t, err)
   226  
   227  		t.Run(
   228  			shouldAuthorizeOrNot(expectAuthorized),
   229  			testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
   230  		)
   231  	}
   232  }
   233  
   234  func testAuthenticateBearerInHeader(
   235  	// build a request with the token as a header, then check against the authorizer
   236  	ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
   237  	extraAsserters ...func(context.Context, *testing.T),
   238  ) func(*testing.T) {
   239  	if parameter == "" {
   240  		parameter = "Bearer"
   241  	}
   242  
   243  	return func(t *testing.T) {
   244  		req, err := http.NewRequestWithContext(ctx, http.MethodGet, authPath, nil)
   245  		require.NoError(t, err)
   246  		req.Header.Set(runtime.HeaderAuthorization, fmt.Sprintf("%s %s", parameter, token))
   247  
   248  		t.Run(
   249  			shouldAuthorizeOrNot(expectAuthorized),
   250  			testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
   251  		)
   252  	}
   253  }
   254  
   255  func testAuthenticateBearerInForm(
   256  	// build a request with the token as a form field, then check against the authorizer
   257  	ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
   258  	extraAsserters ...func(context.Context, *testing.T),
   259  ) func(*testing.T) {
   260  	if parameter == "" {
   261  		parameter = accessTokenParam
   262  	}
   263  
   264  	return func(t *testing.T) {
   265  		body := url.Values(map[string][]string{})
   266  		body.Set(parameter, token)
   267  		req, err := http.NewRequestWithContext(ctx, http.MethodPost, authPath, strings.NewReader(body.Encode()))
   268  		require.NoError(t, err)
   269  		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   270  
   271  		t.Run(
   272  			shouldAuthorizeOrNot(expectAuthorized),
   273  			testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
   274  		)
   275  	}
   276  }
   277  func testAuthenticateBearerInMultipartForm(
   278  	// build a request with the token as a multipart form field, then check against the authorizer
   279  	ctx context.Context, authorizer runtime.Authenticator, parameter, token string, expectAuthorized authExpectation,
   280  	extraAsserters ...func(context.Context, *testing.T),
   281  ) func(*testing.T) {
   282  	if parameter == "" {
   283  		parameter = accessTokenParam
   284  	}
   285  
   286  	return func(t *testing.T) {
   287  		body := bytes.NewBuffer(nil)
   288  		writer := multipart.NewWriter(body)
   289  		require.NoError(t, writer.WriteField(parameter, token))
   290  		require.NoError(t, writer.Close())
   291  		req, err := http.NewRequestWithContext(ctx, http.MethodPost, authPath, body)
   292  		require.NoError(t, err)
   293  		req.Header.Set("Content-Type", writer.FormDataContentType())
   294  
   295  		t.Run(
   296  			shouldAuthorizeOrNot(expectAuthorized),
   297  			testIsAuthorized(ctx, req, authorizer, expectAuthorized, extraAsserters...),
   298  		)
   299  	}
   300  }
   301  

View as plain text