github.com/verrazzano/verrazzano@v1.7.0/authproxy/src/auth/token_test.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package auth
     5  
     6  import (
     7  	"context"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net/http"
    12  	"testing"
    13  
    14  	"github.com/coreos/go-oidc/v3/oidc"
    15  	"github.com/stretchr/testify/assert"
    16  	"go.uber.org/zap"
    17  )
    18  
    19  // TestAuthenticateToken tests that tokens are properly processed and validated
    20  // GIVEN a request to authenticate a token
    21  // WHEN  the request is processed
    22  // THEN  the proper validation result is returned
    23  func TestAuthenticateToken(t *testing.T) {
    24  	validToken := "token-valid"
    25  	issuer := "test-issuer"
    26  	authenticator := OIDCAuthenticator{
    27  		Log: zap.S(),
    28  		oidcConfig: &OIDCConfiguration{
    29  			ServiceURL: issuer,
    30  		},
    31  	}
    32  	verifier := newMockVerifier(issuer, validToken)
    33  	authenticator.verifier.Store(verifier)
    34  
    35  	tests := []struct {
    36  		name             string
    37  		token            string
    38  		expectValidation bool
    39  	}{
    40  		{
    41  			name:             "valid token provided",
    42  			token:            validToken,
    43  			expectValidation: true,
    44  		},
    45  		{
    46  			name:             "invalid token provided",
    47  			token:            "token-invalid",
    48  			expectValidation: false,
    49  		},
    50  	}
    51  	for _, tt := range tests {
    52  		t.Run(tt.name, func(t *testing.T) {
    53  			validated, err := authenticator.AuthenticateToken(context.TODO(), tt.token)
    54  			if tt.expectValidation {
    55  				assert.NoError(t, err)
    56  				assert.True(t, validated)
    57  				return
    58  			}
    59  			assert.Error(t, err)
    60  			assert.False(t, validated)
    61  		})
    62  	}
    63  }
    64  
    65  // TestGetTokenFromAuthHeader tests that the token can be extracted from an auth header
    66  // GIVEN an auth header
    67  // WHEN  the bearer token is properly formatted
    68  // THEN  the token value is returned
    69  func TestGetTokenFromAuthHeader(t *testing.T) {
    70  	tests := []struct {
    71  		name          string
    72  		authHeader    string
    73  		expectedToken string
    74  		expectError   bool
    75  	}{
    76  		{
    77  			name:        "empty auth header",
    78  			expectError: true,
    79  		},
    80  		{
    81  			name:        "no token",
    82  			authHeader:  "Bearer",
    83  			expectError: true,
    84  		},
    85  		{
    86  			name:          "valid token",
    87  			authHeader:    "Bearer token",
    88  			expectedToken: "token",
    89  		},
    90  		{
    91  			name:          "token with params",
    92  			authHeader:    "Bearer token param1 param2",
    93  			expectedToken: "token",
    94  		},
    95  	}
    96  	for _, tt := range tests {
    97  		t.Run(tt.name, func(t *testing.T) {
    98  			token, err := getTokenFromAuthHeader(tt.authHeader)
    99  			if tt.expectError {
   100  				assert.Error(t, err)
   101  				return
   102  			}
   103  
   104  			assert.NoError(t, err)
   105  			assert.Equal(t, tt.expectedToken, token)
   106  		})
   107  	}
   108  }
   109  
   110  // TestInitServiceOIDCVerifier tests that the OIDC verifier can be properly initialized
   111  func TestInitServiceOIDCVerifier(t *testing.T) {
   112  	authenticator := OIDCAuthenticator{
   113  		Log: zap.S(),
   114  	}
   115  
   116  	// GIVEN a valid configuration
   117  	// WHEN  the service URL is not set
   118  	// THEN  an error is returned
   119  	err := authenticator.initServiceOIDCVerifier()
   120  	assert.Error(t, err)
   121  
   122  	issuer := "test-issuer"
   123  	authenticator.oidcConfig = &OIDCConfiguration{
   124  		ServiceURL: issuer,
   125  	}
   126  	// GIVEN a valid configuration
   127  	// WHEN  the service URL is set
   128  	// THEN  no error is returned
   129  	err = authenticator.initServiceOIDCVerifier()
   130  	assert.Error(t, err)
   131  
   132  }
   133  
   134  // TestLoadVerifier tests loading the verifier object from the atomic source
   135  func TestLoadVerifier(t *testing.T) {
   136  	authenticator := OIDCAuthenticator{
   137  		Log: zap.S(),
   138  	}
   139  
   140  	// GIVEN an Authenticator object
   141  	// WHEN  the verifier is not set
   142  	// THEN  an error is returned
   143  	_, err := authenticator.loadVerifier()
   144  	assert.Error(t, err)
   145  
   146  	// GIVEN an Authenticator object
   147  	// WHEN  the verifier is not the correct value
   148  	// THEN  an error is returned
   149  	authenticator.verifier.Store("incorrect value")
   150  	_, err = authenticator.loadVerifier()
   151  	assert.Error(t, err)
   152  
   153  	// GIVEN an Authenticator object
   154  	// WHEN  the verifier is correctly set
   155  	// THEN  no error is returned
   156  	authenticator = OIDCAuthenticator{
   157  		Log: zap.S(),
   158  	}
   159  	authenticator.verifier.Store(newMockVerifier("", ""))
   160  	v, err := authenticator.loadVerifier()
   161  	assert.NoError(t, err)
   162  	assert.NotNil(t, v)
   163  	assert.Implements(t, (*verifier)(nil), v)
   164  }
   165  
   166  // TestGetImpersonationHeadersFromRequest tests that the impersonation user and groups can be collected from a request
   167  func TestGetImpersonationHeadersFromRequest(t *testing.T) {
   168  	testUser := "user"
   169  	testGroups := []string{
   170  		"group1",
   171  		"group2",
   172  	}
   173  
   174  	testImp := ImpersonationHeaders{
   175  		User:   testUser,
   176  		Groups: testGroups,
   177  	}
   178  
   179  	impJSON, err := json.Marshal(testImp)
   180  	assert.NoError(t, err)
   181  	validToken := fmt.Sprintf("info.%s.info", base64.RawURLEncoding.EncodeToString(impJSON))
   182  
   183  	tests := []struct {
   184  		name           string
   185  		token          string
   186  		expectedUser   string
   187  		expectedGroups []string
   188  		expectError    bool
   189  	}{
   190  		// GIVEN a request with a valid token
   191  		// WHEN  the request is evaluated
   192  		// THEN  the expected users and groups are populated
   193  		{
   194  			name:           "valid token provided",
   195  			token:          validToken,
   196  			expectedUser:   testUser,
   197  			expectedGroups: testGroups,
   198  		},
   199  		// GIVEN a request with a bad JWT token
   200  		// WHEN  the request is evaluated
   201  		// THEN  an error is returned
   202  		{
   203  			name:        "malformed token provided",
   204  			token:       "token-invalid",
   205  			expectError: true,
   206  		},
   207  		// GIVEN a request with an empty token body
   208  		// WHEN  the request is evaluated
   209  		// THEN  no error is returned
   210  		{
   211  			name:  "empty token provided",
   212  			token: fmt.Sprintf("info.%s.info", base64.RawURLEncoding.EncodeToString([]byte("{}"))),
   213  		},
   214  	}
   215  	for _, tt := range tests {
   216  		t.Run(tt.name, func(t *testing.T) {
   217  			req := http.Request{
   218  				Header: map[string][]string{
   219  					authHeaderKey: {"Bearer " + tt.token},
   220  				},
   221  			}
   222  			imp, err := GetImpersonationHeadersFromRequest(&req)
   223  			if tt.expectError {
   224  				assert.Error(t, err)
   225  				return
   226  			}
   227  			assert.NoError(t, err)
   228  			assert.Equal(t, tt.expectedUser, imp.User)
   229  			assert.ElementsMatch(t, tt.expectedGroups, imp.Groups)
   230  		})
   231  	}
   232  }
   233  
   234  func (m mockVerifier) Verify(_ context.Context, rawIDToken string) (*oidc.IDToken, error) {
   235  	if rawIDToken != m.token {
   236  		return nil, fmt.Errorf("provided token does not match the mocked token")
   237  	}
   238  	return &oidc.IDToken{Issuer: m.issuer}, nil
   239  }
   240  
   241  func newMockVerifier(issuer, token string) *mockVerifier {
   242  	return &mockVerifier{
   243  		issuer: issuer,
   244  		token:  token,
   245  	}
   246  }