github.com/argoproj/argo-cd/v3@v3.2.1/util/session/sessionmanager_test.go (about)

     1  package session
     2  
     3  import (
     4  	"context"
     5  	"encoding/pem"
     6  	stderrors "errors"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/golang-jwt/jwt/v5"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"google.golang.org/grpc/codes"
    21  	"google.golang.org/grpc/status"
    22  	corev1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/client-go/kubernetes/fake"
    26  
    27  	"github.com/argoproj/argo-cd/v3/common"
    28  	appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    29  	apps "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
    30  	"github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
    31  	"github.com/argoproj/argo-cd/v3/test"
    32  	jwtutil "github.com/argoproj/argo-cd/v3/util/jwt"
    33  	"github.com/argoproj/argo-cd/v3/util/password"
    34  	"github.com/argoproj/argo-cd/v3/util/settings"
    35  	utiltest "github.com/argoproj/argo-cd/v3/util/test"
    36  )
    37  
    38  func getProjLister(objects ...runtime.Object) v1alpha1.AppProjectNamespaceLister {
    39  	return test.NewFakeProjListerFromInterface(apps.NewSimpleClientset(objects...).ArgoprojV1alpha1().AppProjects("argocd"))
    40  }
    41  
    42  func getKubeClient(t *testing.T, pass string, enabled bool, capabilities ...settings.AccountCapability) *fake.Clientset {
    43  	t.Helper()
    44  	const defaultSecretKey = "Hello, world!"
    45  
    46  	bcrypt, err := password.HashPassword(pass)
    47  	require.NoError(t, err)
    48  	if len(capabilities) == 0 {
    49  		capabilities = []settings.AccountCapability{settings.AccountCapabilityLogin, settings.AccountCapabilityApiKey}
    50  	}
    51  	var capabilitiesStr []string
    52  	for i := range capabilities {
    53  		capabilitiesStr = append(capabilitiesStr, string(capabilities[i]))
    54  	}
    55  
    56  	return fake.NewClientset(&corev1.ConfigMap{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name:      "argocd-cm",
    59  			Namespace: "argocd",
    60  			Labels: map[string]string{
    61  				"app.kubernetes.io/part-of": "argocd",
    62  			},
    63  		},
    64  		Data: map[string]string{
    65  			"admin":         strings.Join(capabilitiesStr, ","),
    66  			"admin.enabled": strconv.FormatBool(enabled),
    67  		},
    68  	}, &corev1.Secret{
    69  		ObjectMeta: metav1.ObjectMeta{
    70  			Name:      "argocd-secret",
    71  			Namespace: "argocd",
    72  		},
    73  		Data: map[string][]byte{
    74  			"admin.password":   []byte(bcrypt),
    75  			"server.secretkey": []byte(defaultSecretKey),
    76  		},
    77  	})
    78  }
    79  
    80  func newSessionManager(settingsMgr *settings.SettingsManager, projectLister v1alpha1.AppProjectNamespaceLister, storage UserStateStorage) *SessionManager {
    81  	mgr := NewSessionManager(settingsMgr, projectLister, "", nil, storage)
    82  	mgr.verificationDelayNoiseEnabled = false
    83  	return mgr
    84  }
    85  
    86  func TestSessionManager_AdminToken(t *testing.T) {
    87  	redisClient, closer := test.NewInMemoryRedis()
    88  	defer closer()
    89  
    90  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", true), "argocd")
    91  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(redisClient))
    92  
    93  	token, err := mgr.Create("admin:login", 0, "123")
    94  	require.NoError(t, err, "Could not create token")
    95  
    96  	claims, newToken, err := mgr.Parse(token)
    97  	require.NoError(t, err)
    98  	assert.Empty(t, newToken)
    99  
   100  	mapClaims := *(claims.(*jwt.MapClaims))
   101  	subject := mapClaims["sub"].(string)
   102  	if subject != "admin" {
   103  		t.Errorf("Token claim subject %q does not match expected subject %q.", subject, "admin")
   104  	}
   105  }
   106  
   107  func TestSessionManager_AdminToken_ExpiringSoon(t *testing.T) {
   108  	redisClient, closer := test.NewInMemoryRedis()
   109  	defer closer()
   110  
   111  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", true), "argocd")
   112  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(redisClient))
   113  
   114  	token, err := mgr.Create("admin:login", int64(autoRegenerateTokenDuration.Seconds()-1), "123")
   115  	require.NoError(t, err)
   116  
   117  	// verify new token is generated is login token is expiring soon
   118  	_, newToken, err := mgr.Parse(token)
   119  	require.NoError(t, err)
   120  	assert.NotEmpty(t, newToken)
   121  
   122  	// verify that new token is valid and for the same user
   123  	claims, _, err := mgr.Parse(newToken)
   124  	require.NoError(t, err)
   125  
   126  	mapClaims := *(claims.(*jwt.MapClaims))
   127  	subject := mapClaims["sub"].(string)
   128  	assert.Equal(t, "admin", subject)
   129  }
   130  
   131  func TestSessionManager_AdminToken_Revoked(t *testing.T) {
   132  	redisClient, closer := test.NewInMemoryRedis()
   133  	defer closer()
   134  
   135  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", true), "argocd")
   136  	storage := NewUserStateStorage(redisClient)
   137  
   138  	mgr := newSessionManager(settingsMgr, getProjLister(), storage)
   139  
   140  	token, err := mgr.Create("admin:login", 0, "123")
   141  	require.NoError(t, err)
   142  
   143  	err = storage.RevokeToken(t.Context(), "123", time.Hour)
   144  	require.NoError(t, err)
   145  
   146  	_, _, err = mgr.Parse(token)
   147  	assert.EqualError(t, err, "token is revoked, please re-login")
   148  }
   149  
   150  func TestSessionManager_AdminToken_Deactivated(t *testing.T) {
   151  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", false), "argocd")
   152  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   153  
   154  	token, err := mgr.Create("admin:login", 0, "abc")
   155  	require.NoError(t, err, "Could not create token")
   156  
   157  	_, _, err = mgr.Parse(token)
   158  	assert.ErrorContains(t, err, "account admin is disabled")
   159  }
   160  
   161  func TestSessionManager_AdminToken_LoginCapabilityDisabled(t *testing.T) {
   162  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", true, settings.AccountCapabilityLogin), "argocd")
   163  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   164  
   165  	token, err := mgr.Create("admin", 0, "abc")
   166  	require.NoError(t, err, "Could not create token")
   167  
   168  	_, _, err = mgr.Parse(token)
   169  	assert.ErrorContains(t, err, "account admin does not have 'apiKey' capability")
   170  }
   171  
   172  func TestSessionManager_ProjectToken(t *testing.T) {
   173  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "pass", true), "argocd")
   174  
   175  	t.Run("Valid Token", func(t *testing.T) {
   176  		proj := appv1.AppProject{
   177  			ObjectMeta: metav1.ObjectMeta{
   178  				Name:      "default",
   179  				Namespace: "argocd",
   180  			},
   181  			Spec: appv1.AppProjectSpec{Roles: []appv1.ProjectRole{{Name: "test"}}},
   182  			Status: appv1.AppProjectStatus{JWTTokensByRole: map[string]appv1.JWTTokens{
   183  				"test": {
   184  					Items: []appv1.JWTToken{{ID: "abc", IssuedAt: time.Now().Unix(), ExpiresAt: 0}},
   185  				},
   186  			}},
   187  		}
   188  		mgr := newSessionManager(settingsMgr, getProjLister(&proj), NewUserStateStorage(nil))
   189  
   190  		jwtToken, err := mgr.Create("proj:default:test", 100, "abc")
   191  		require.NoError(t, err)
   192  
   193  		claims, _, err := mgr.Parse(jwtToken)
   194  		require.NoError(t, err)
   195  
   196  		mapClaims, err := jwtutil.MapClaims(claims)
   197  		require.NoError(t, err)
   198  
   199  		assert.Equal(t, "proj:default:test", mapClaims["sub"])
   200  	})
   201  
   202  	t.Run("Token Revoked", func(t *testing.T) {
   203  		proj := appv1.AppProject{
   204  			ObjectMeta: metav1.ObjectMeta{
   205  				Name:      "default",
   206  				Namespace: "argocd",
   207  			},
   208  			Spec: appv1.AppProjectSpec{Roles: []appv1.ProjectRole{{Name: "test"}}},
   209  		}
   210  
   211  		mgr := newSessionManager(settingsMgr, getProjLister(&proj), NewUserStateStorage(nil))
   212  
   213  		jwtToken, err := mgr.Create("proj:default:test", 10, "")
   214  		require.NoError(t, err)
   215  
   216  		_, _, err = mgr.Parse(jwtToken)
   217  		assert.ErrorContains(t, err, "does not exist in project 'default'")
   218  	})
   219  }
   220  
   221  type tokenVerifierMock struct {
   222  	claims jwt.Claims
   223  	err    error
   224  }
   225  
   226  func (tm *tokenVerifierMock) VerifyToken(_ string) (jwt.Claims, string, error) {
   227  	if tm.claims == nil {
   228  		return nil, "", tm.err
   229  	}
   230  	return tm.claims, "", tm.err
   231  }
   232  
   233  func strPointer(str string) *string {
   234  	return &str
   235  }
   236  
   237  func TestSessionManager_WithAuthMiddleware(t *testing.T) {
   238  	handlerFunc := func() func(http.ResponseWriter, *http.Request) {
   239  		return func(w http.ResponseWriter, _ *http.Request) {
   240  			t.Helper()
   241  			w.WriteHeader(http.StatusOK)
   242  			w.Header().Set("Content-Type", "application/text")
   243  			_, err := w.Write([]byte("Ok"))
   244  			require.NoError(t, err, "error writing response: %s", err)
   245  		}
   246  	}
   247  	type testCase struct {
   248  		name                 string
   249  		authDisabled         bool
   250  		cookieHeader         bool
   251  		verifiedClaims       *jwt.RegisteredClaims
   252  		verifyTokenErr       error
   253  		expectedStatusCode   int
   254  		expectedResponseBody *string
   255  	}
   256  
   257  	cases := []testCase{
   258  		{
   259  			name:                 "will authenticate successfully",
   260  			authDisabled:         false,
   261  			cookieHeader:         true,
   262  			verifiedClaims:       &jwt.RegisteredClaims{},
   263  			verifyTokenErr:       nil,
   264  			expectedStatusCode:   http.StatusOK,
   265  			expectedResponseBody: strPointer("Ok"),
   266  		},
   267  		{
   268  			name:                 "will be noop if auth is disabled",
   269  			authDisabled:         true,
   270  			cookieHeader:         false,
   271  			verifiedClaims:       nil,
   272  			verifyTokenErr:       nil,
   273  			expectedStatusCode:   http.StatusOK,
   274  			expectedResponseBody: strPointer("Ok"),
   275  		},
   276  		{
   277  			name:                 "will return 400 if no cookie header",
   278  			authDisabled:         false,
   279  			cookieHeader:         false,
   280  			verifiedClaims:       &jwt.RegisteredClaims{},
   281  			verifyTokenErr:       nil,
   282  			expectedStatusCode:   http.StatusBadRequest,
   283  			expectedResponseBody: nil,
   284  		},
   285  		{
   286  			name:                 "will return 401 verify token fails",
   287  			authDisabled:         false,
   288  			cookieHeader:         true,
   289  			verifiedClaims:       &jwt.RegisteredClaims{},
   290  			verifyTokenErr:       stderrors.New("token error"),
   291  			expectedStatusCode:   http.StatusUnauthorized,
   292  			expectedResponseBody: nil,
   293  		},
   294  		{
   295  			name:                 "will return 200 if claims are nil",
   296  			authDisabled:         false,
   297  			cookieHeader:         true,
   298  			verifiedClaims:       nil,
   299  			verifyTokenErr:       nil,
   300  			expectedStatusCode:   http.StatusOK,
   301  			expectedResponseBody: strPointer("Ok"),
   302  		},
   303  	}
   304  	for _, tc := range cases {
   305  		tc := tc
   306  		t.Run(tc.name, func(t *testing.T) {
   307  			// given
   308  			mux := http.NewServeMux()
   309  			mux.HandleFunc("/", handlerFunc())
   310  			tm := &tokenVerifierMock{
   311  				claims: tc.verifiedClaims,
   312  				err:    tc.verifyTokenErr,
   313  			}
   314  			ts := httptest.NewServer(WithAuthMiddleware(tc.authDisabled, tm, mux))
   315  			defer ts.Close()
   316  			req, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody)
   317  			require.NoErrorf(t, err, "error creating request: %s", err)
   318  			if tc.cookieHeader {
   319  				req.Header.Add("Cookie", "argocd.token=123456")
   320  			}
   321  
   322  			// when
   323  			resp, err := http.DefaultClient.Do(req)
   324  
   325  			// then
   326  			require.NoError(t, err)
   327  			assert.NotNil(t, resp)
   328  			assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
   329  			if tc.expectedResponseBody != nil {
   330  				body, err := io.ReadAll(resp.Body)
   331  				require.NoError(t, err)
   332  				actual := strings.TrimSuffix(string(body), "\n")
   333  				assert.Contains(t, actual, *tc.expectedResponseBody)
   334  			}
   335  		})
   336  	}
   337  }
   338  
   339  var (
   340  	loggedOutContext = context.Background()
   341  	//nolint:staticcheck
   342  	loggedInContext = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "bar", "groups": []string{"baz"}})
   343  	//nolint:staticcheck
   344  	loggedInContextFederated = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"iss": "qux", "sub": "not-foo", "email": "bar", "groups": []string{"baz"}, "federated_claims": map[string]any{"user_id": "foo"}})
   345  )
   346  
   347  func TestIss(t *testing.T) {
   348  	assert.Empty(t, Iss(loggedOutContext))
   349  	assert.Equal(t, "qux", Iss(loggedInContext))
   350  	assert.Equal(t, "qux", Iss(loggedInContextFederated))
   351  }
   352  
   353  func TestLoggedIn(t *testing.T) {
   354  	assert.False(t, LoggedIn(loggedOutContext))
   355  	assert.True(t, LoggedIn(loggedInContext))
   356  	assert.True(t, LoggedIn(loggedInContextFederated))
   357  }
   358  
   359  func TestUsername(t *testing.T) {
   360  	assert.Empty(t, Username(loggedOutContext))
   361  	assert.Equal(t, "bar", Username(loggedInContext))
   362  	assert.Equal(t, "bar", Username(loggedInContextFederated))
   363  }
   364  
   365  func TestGetUserIdentifier(t *testing.T) {
   366  	assert.Empty(t, GetUserIdentifier(loggedOutContext))
   367  	assert.Equal(t, "foo", GetUserIdentifier(loggedInContext))
   368  	assert.Equal(t, "foo", GetUserIdentifier(loggedInContextFederated))
   369  }
   370  
   371  func TestGroups(t *testing.T) {
   372  	assert.Empty(t, Groups(loggedOutContext, []string{"groups"}))
   373  	assert.Equal(t, []string{"baz"}, Groups(loggedInContext, []string{"groups"}))
   374  }
   375  
   376  func TestVerifyUsernamePassword(t *testing.T) {
   377  	const password = "password"
   378  
   379  	for _, tc := range []struct {
   380  		name     string
   381  		disabled bool
   382  		userName string
   383  		password string
   384  		expected error
   385  	}{
   386  		{
   387  			name:     "Success if userName and password is correct",
   388  			disabled: false,
   389  			userName: common.ArgoCDAdminUsername,
   390  			password: password,
   391  			expected: nil,
   392  		},
   393  		{
   394  			name:     "Return error if password is empty",
   395  			disabled: false,
   396  			userName: common.ArgoCDAdminUsername,
   397  			password: "",
   398  			expected: status.Errorf(codes.Unauthenticated, blankPasswordError),
   399  		},
   400  		{
   401  			name:     "Return error if password is not correct",
   402  			disabled: false,
   403  			userName: common.ArgoCDAdminUsername,
   404  			password: "foo",
   405  			expected: status.Errorf(codes.Unauthenticated, invalidLoginError),
   406  		},
   407  		{
   408  			name:     "Return error if disableAdmin is true",
   409  			disabled: true,
   410  			userName: common.ArgoCDAdminUsername,
   411  			password: password,
   412  			expected: status.Errorf(codes.Unauthenticated, accountDisabled, "admin"),
   413  		},
   414  	} {
   415  		t.Run(tc.name, func(t *testing.T) {
   416  			settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, password, !tc.disabled), "argocd")
   417  
   418  			mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   419  
   420  			err := mgr.VerifyUsernamePassword(tc.userName, tc.password)
   421  
   422  			if tc.expected == nil {
   423  				require.NoError(t, err)
   424  			} else {
   425  				assert.EqualError(t, err, tc.expected.Error())
   426  			}
   427  		})
   428  	}
   429  }
   430  
   431  func TestCacheValueGetters(t *testing.T) {
   432  	t.Run("Default values", func(t *testing.T) {
   433  		mlf := getMaxLoginFailures()
   434  		assert.Equal(t, defaultMaxLoginFailures, mlf)
   435  
   436  		mcs := getMaximumCacheSize()
   437  		assert.Equal(t, defaultMaxCacheSize, mcs)
   438  	})
   439  
   440  	t.Run("Valid environment overrides", func(t *testing.T) {
   441  		t.Setenv(envLoginMaxFailCount, "5")
   442  		t.Setenv(envLoginMaxCacheSize, "5")
   443  
   444  		mlf := getMaxLoginFailures()
   445  		assert.Equal(t, 5, mlf)
   446  
   447  		mcs := getMaximumCacheSize()
   448  		assert.Equal(t, 5, mcs)
   449  	})
   450  
   451  	t.Run("Invalid environment overrides", func(t *testing.T) {
   452  		t.Setenv(envLoginMaxFailCount, "invalid")
   453  		t.Setenv(envLoginMaxCacheSize, "invalid")
   454  
   455  		mlf := getMaxLoginFailures()
   456  		assert.Equal(t, defaultMaxLoginFailures, mlf)
   457  
   458  		mcs := getMaximumCacheSize()
   459  		assert.Equal(t, defaultMaxCacheSize, mcs)
   460  	})
   461  
   462  	t.Run("Less than allowed in environment overrides", func(t *testing.T) {
   463  		t.Setenv(envLoginMaxFailCount, "-1")
   464  		t.Setenv(envLoginMaxCacheSize, "-1")
   465  
   466  		mlf := getMaxLoginFailures()
   467  		assert.Equal(t, defaultMaxLoginFailures, mlf)
   468  
   469  		mcs := getMaximumCacheSize()
   470  		assert.Equal(t, defaultMaxCacheSize, mcs)
   471  	})
   472  
   473  	t.Run("Greater than allowed in environment overrides", func(t *testing.T) {
   474  		t.Setenv(envLoginMaxFailCount, strconv.Itoa(math.MaxInt32+1))
   475  		t.Setenv(envLoginMaxCacheSize, strconv.Itoa(math.MaxInt32+1))
   476  
   477  		mlf := getMaxLoginFailures()
   478  		assert.Equal(t, defaultMaxLoginFailures, mlf)
   479  
   480  		mcs := getMaximumCacheSize()
   481  		assert.Equal(t, defaultMaxCacheSize, mcs)
   482  	})
   483  }
   484  
   485  func TestLoginRateLimiter(t *testing.T) {
   486  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "password", true), "argocd")
   487  	storage := NewUserStateStorage(nil)
   488  
   489  	mgr := newSessionManager(settingsMgr, getProjLister(), storage)
   490  
   491  	t.Run("Test login delay valid user", func(t *testing.T) {
   492  		for i := 0; i < getMaxLoginFailures(); i++ {
   493  			err := mgr.VerifyUsernamePassword("admin", "wrong")
   494  			require.Error(t, err)
   495  		}
   496  
   497  		// The 11th time should fail even if password is right
   498  		{
   499  			err := mgr.VerifyUsernamePassword("admin", "password")
   500  			require.Error(t, err)
   501  		}
   502  
   503  		storage.attempts = map[string]LoginAttempts{}
   504  		// Failed counter should have been reset, should validate immediately
   505  		{
   506  			err := mgr.VerifyUsernamePassword("admin", "password")
   507  			require.NoError(t, err)
   508  		}
   509  	})
   510  
   511  	t.Run("Test login delay invalid user", func(t *testing.T) {
   512  		for i := 0; i < getMaxLoginFailures(); i++ {
   513  			err := mgr.VerifyUsernamePassword("invalid", "wrong")
   514  			require.Error(t, err)
   515  		}
   516  
   517  		err := mgr.VerifyUsernamePassword("invalid", "wrong")
   518  		require.Error(t, err)
   519  	})
   520  }
   521  
   522  func TestMaxUsernameLength(t *testing.T) {
   523  	username := ""
   524  	for i := 0; i < maxUsernameLength+1; i++ {
   525  		username += "a"
   526  	}
   527  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "password", true), "argocd")
   528  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   529  	err := mgr.VerifyUsernamePassword(username, "password")
   530  	assert.ErrorContains(t, err, fmt.Sprintf(usernameTooLongError, maxUsernameLength))
   531  }
   532  
   533  func TestMaxCacheSize(t *testing.T) {
   534  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "password", true), "argocd")
   535  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   536  
   537  	invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}
   538  	// Temporarily decrease max cache size
   539  	t.Setenv(envLoginMaxCacheSize, "5")
   540  
   541  	for _, user := range invalidUsers {
   542  		err := mgr.VerifyUsernamePassword(user, "password")
   543  		require.Error(t, err)
   544  	}
   545  
   546  	assert.Len(t, mgr.GetLoginFailures(), 5)
   547  }
   548  
   549  func TestFailedAttemptsExpiry(t *testing.T) {
   550  	settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClient(t, "password", true), "argocd")
   551  	mgr := newSessionManager(settingsMgr, getProjLister(), NewUserStateStorage(nil))
   552  
   553  	invalidUsers := []string{"invalid1", "invalid2", "invalid3", "invalid4", "invalid5", "invalid6", "invalid7"}
   554  
   555  	t.Setenv(envLoginFailureWindowSeconds, "1")
   556  
   557  	for _, user := range invalidUsers {
   558  		err := mgr.VerifyUsernamePassword(user, "password")
   559  		require.Error(t, err)
   560  	}
   561  
   562  	time.Sleep(2 * time.Second)
   563  
   564  	err := mgr.VerifyUsernamePassword("invalid8", "password")
   565  	require.Error(t, err)
   566  	assert.Len(t, mgr.GetLoginFailures(), 1)
   567  }
   568  
   569  func getKubeClientWithConfig(config map[string]string, secretConfig map[string][]byte) *fake.Clientset {
   570  	mergedSecretConfig := map[string][]byte{
   571  		"server.secretkey": []byte("Hello, world!"),
   572  	}
   573  	for key, value := range secretConfig {
   574  		mergedSecretConfig[key] = value
   575  	}
   576  
   577  	return fake.NewClientset(&corev1.ConfigMap{
   578  		ObjectMeta: metav1.ObjectMeta{
   579  			Name:      "argocd-cm",
   580  			Namespace: "argocd",
   581  			Labels: map[string]string{
   582  				"app.kubernetes.io/part-of": "argocd",
   583  			},
   584  		},
   585  		Data: config,
   586  	}, &corev1.Secret{
   587  		ObjectMeta: metav1.ObjectMeta{
   588  			Name:      "argocd-secret",
   589  			Namespace: "argocd",
   590  		},
   591  		Data: mergedSecretConfig,
   592  	})
   593  }
   594  
   595  func TestSessionManager_VerifyToken(t *testing.T) {
   596  	oidcTestServer := utiltest.GetOIDCTestServer(t, nil)
   597  	t.Cleanup(oidcTestServer.Close)
   598  
   599  	dexTestServer := utiltest.GetDexTestServer(t)
   600  	t.Cleanup(dexTestServer.Close)
   601  
   602  	t.Run("RS512 is supported", func(t *testing.T) {
   603  		dexConfig := map[string]string{
   604  			"url": "",
   605  			"oidc.config": fmt.Sprintf(`
   606  name: Test
   607  issuer: %s
   608  clientID: xxx
   609  clientSecret: yyy
   610  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   611  		}
   612  
   613  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, nil), "argocd")
   614  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   615  		mgr.verificationDelayNoiseEnabled = false
   616  		// Use test server's client to avoid TLS issues.
   617  		mgr.client = oidcTestServer.Client()
   618  
   619  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   620  		claims.Issuer = oidcTestServer.URL
   621  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   622  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   623  		require.NoError(t, err)
   624  		tokenString, err := token.SignedString(key)
   625  		require.NoError(t, err)
   626  
   627  		_, _, err = mgr.VerifyToken(tokenString)
   628  		assert.NotContains(t, err.Error(), "oidc: id token signed with unsupported algorithm")
   629  	})
   630  
   631  	t.Run("oidcConfig.rootCA is respected", func(t *testing.T) {
   632  		cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: oidcTestServer.TLS.Certificates[0].Certificate[0]})
   633  
   634  		dexConfig := map[string]string{
   635  			"url": "",
   636  			"oidc.config": fmt.Sprintf(`
   637  name: Test
   638  issuer: %s
   639  clientID: xxx
   640  clientSecret: yyy
   641  requestedScopes: ["oidc"]
   642  rootCA: |
   643    %s
   644  `, oidcTestServer.URL, strings.ReplaceAll(string(cert), "\n", "\n  ")),
   645  		}
   646  
   647  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, nil), "argocd")
   648  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   649  		mgr.verificationDelayNoiseEnabled = false
   650  
   651  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   652  		claims.Issuer = oidcTestServer.URL
   653  		token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
   654  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   655  		require.NoError(t, err)
   656  		tokenString, err := token.SignedString(key)
   657  		require.NoError(t, err)
   658  
   659  		_, _, err = mgr.VerifyToken(tokenString)
   660  		// If the root CA is being respected, we won't get this error. The error message is environment-dependent, so
   661  		// we check for either of the error messages associated with a failed cert check.
   662  		assert.NotContains(t, err.Error(), "certificate is not trusted")
   663  		assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
   664  	})
   665  
   666  	t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
   667  		dexConfig := map[string]string{
   668  			"url": dexTestServer.URL,
   669  			"dex.config": `connectors:
   670  - type: github
   671    name: GitHub
   672    config:
   673      clientID: aabbccddeeff00112233
   674      clientSecret: aabbccddeeff00112233`,
   675  		}
   676  
   677  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   678  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   679  		secretConfig := map[string][]byte{
   680  			"tls.crt": utiltest.Cert,
   681  			"tls.key": utiltest.PrivateKey,
   682  		}
   683  
   684  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
   685  		mgr := NewSessionManager(settingsMgr, getProjLister(), dexTestServer.URL, nil, NewUserStateStorage(nil))
   686  		mgr.verificationDelayNoiseEnabled = false
   687  
   688  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   689  		claims.Issuer = dexTestServer.URL + "/api/dex"
   690  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   691  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   692  		require.NoError(t, err)
   693  		tokenString, err := token.SignedString(key)
   694  		require.NoError(t, err)
   695  
   696  		_, _, err = mgr.VerifyToken(tokenString)
   697  		require.Error(t, err)
   698  		assert.ErrorIs(t, err, common.ErrTokenVerification)
   699  	})
   700  
   701  	t.Run("OIDC provider is external, TLS is configured", func(t *testing.T) {
   702  		dexConfig := map[string]string{
   703  			"url": "",
   704  			"oidc.config": fmt.Sprintf(`
   705  name: Test
   706  issuer: %s
   707  clientID: xxx
   708  clientSecret: yyy
   709  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   710  		}
   711  
   712  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   713  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   714  		secretConfig := map[string][]byte{
   715  			"tls.crt": utiltest.Cert,
   716  			"tls.key": utiltest.PrivateKey,
   717  		}
   718  
   719  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
   720  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   721  		mgr.verificationDelayNoiseEnabled = false
   722  
   723  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   724  		claims.Issuer = oidcTestServer.URL
   725  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   726  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   727  		require.NoError(t, err)
   728  		tokenString, err := token.SignedString(key)
   729  		require.NoError(t, err)
   730  
   731  		_, _, err = mgr.VerifyToken(tokenString)
   732  		require.Error(t, err)
   733  		assert.ErrorIs(t, err, common.ErrTokenVerification)
   734  	})
   735  
   736  	t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
   737  		dexConfig := map[string]string{
   738  			"url": dexTestServer.URL,
   739  			"dex.config": `connectors:
   740  - type: github
   741    name: GitHub
   742    config:
   743      clientID: aabbccddeeff00112233
   744      clientSecret: aabbccddeeff00112233`,
   745  		}
   746  
   747  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   748  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   749  		secretConfig := map[string][]byte{
   750  			"tls.crt": utiltest.Cert,
   751  			"tls.key": utiltest.PrivateKey,
   752  		}
   753  
   754  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
   755  		mgr := NewSessionManager(settingsMgr, getProjLister(), dexTestServer.URL, nil, NewUserStateStorage(nil))
   756  		mgr.verificationDelayNoiseEnabled = false
   757  
   758  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   759  		claims.Issuer = dexTestServer.URL + "/api/dex"
   760  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   761  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   762  		require.NoError(t, err)
   763  		tokenString, err := token.SignedString(key)
   764  		require.NoError(t, err)
   765  
   766  		_, _, err = mgr.VerifyToken(tokenString)
   767  		require.Error(t, err)
   768  		assert.ErrorIs(t, err, common.ErrTokenVerification)
   769  	})
   770  
   771  	t.Run("OIDC provider is external, TLS is configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
   772  		dexConfig := map[string]string{
   773  			"url": "",
   774  			"oidc.config": fmt.Sprintf(`
   775  name: Test
   776  issuer: %s
   777  clientID: xxx
   778  clientSecret: yyy
   779  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   780  			"oidc.tls.insecure.skip.verify": "true",
   781  		}
   782  
   783  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   784  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   785  		secretConfig := map[string][]byte{
   786  			"tls.crt": utiltest.Cert,
   787  			"tls.key": utiltest.PrivateKey,
   788  		}
   789  
   790  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, secretConfig), "argocd")
   791  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   792  		mgr.verificationDelayNoiseEnabled = false
   793  
   794  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   795  		claims.Issuer = oidcTestServer.URL
   796  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   797  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   798  		require.NoError(t, err)
   799  		tokenString, err := token.SignedString(key)
   800  		require.NoError(t, err)
   801  
   802  		_, _, err = mgr.VerifyToken(tokenString)
   803  		assert.NotContains(t, err.Error(), "certificate is not trusted")
   804  		assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
   805  	})
   806  
   807  	t.Run("OIDC provider is external, TLS is not configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
   808  		dexConfig := map[string]string{
   809  			"url": "",
   810  			"oidc.config": fmt.Sprintf(`
   811  name: Test
   812  issuer: %s
   813  clientID: xxx
   814  clientSecret: yyy
   815  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   816  			"oidc.tls.insecure.skip.verify": "true",
   817  		}
   818  
   819  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(dexConfig, nil), "argocd")
   820  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   821  		mgr.verificationDelayNoiseEnabled = false
   822  
   823  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"test-client"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   824  		claims.Issuer = oidcTestServer.URL
   825  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   826  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   827  		require.NoError(t, err)
   828  		tokenString, err := token.SignedString(key)
   829  		require.NoError(t, err)
   830  
   831  		_, _, err = mgr.VerifyToken(tokenString)
   832  		// This is the error thrown when the test server's certificate _is_ being verified.
   833  		assert.NotContains(t, err.Error(), "certificate is not trusted")
   834  		assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
   835  	})
   836  
   837  	t.Run("OIDC provider is external, audience is not specified", func(t *testing.T) {
   838  		config := map[string]string{
   839  			"url": "",
   840  			"oidc.config": fmt.Sprintf(`
   841  name: Test
   842  issuer: %s
   843  clientID: xxx
   844  clientSecret: yyy
   845  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   846  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
   847  		}
   848  
   849  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   850  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   851  		secretConfig := map[string][]byte{
   852  			"tls.crt": utiltest.Cert,
   853  			"tls.key": utiltest.PrivateKey,
   854  		}
   855  
   856  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
   857  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   858  		mgr.verificationDelayNoiseEnabled = false
   859  
   860  		claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   861  		claims.Issuer = oidcTestServer.URL
   862  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   863  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   864  		require.NoError(t, err)
   865  		tokenString, err := token.SignedString(key)
   866  		require.NoError(t, err)
   867  
   868  		_, _, err = mgr.VerifyToken(tokenString)
   869  		require.Error(t, err)
   870  	})
   871  
   872  	t.Run("OIDC provider is external, audience is not specified, absent audience is allowed", func(t *testing.T) {
   873  		config := map[string]string{
   874  			"url": "",
   875  			"oidc.config": fmt.Sprintf(`
   876  name: Test
   877  issuer: %s
   878  clientID: xxx
   879  clientSecret: yyy
   880  requestedScopes: ["oidc"]
   881  skipAudienceCheckWhenTokenHasNoAudience: true`, oidcTestServer.URL),
   882  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
   883  		}
   884  
   885  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   886  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   887  		secretConfig := map[string][]byte{
   888  			"tls.crt": utiltest.Cert,
   889  			"tls.key": utiltest.PrivateKey,
   890  		}
   891  
   892  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
   893  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   894  		mgr.verificationDelayNoiseEnabled = false
   895  
   896  		claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   897  		claims.Issuer = oidcTestServer.URL
   898  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   899  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   900  		require.NoError(t, err)
   901  		tokenString, err := token.SignedString(key)
   902  		require.NoError(t, err)
   903  
   904  		_, _, err = mgr.VerifyToken(tokenString)
   905  		require.NoError(t, err)
   906  	})
   907  
   908  	t.Run("OIDC provider is external, audience is not specified but is required", func(t *testing.T) {
   909  		config := map[string]string{
   910  			"url": "",
   911  			"oidc.config": fmt.Sprintf(`
   912  name: Test
   913  issuer: %s
   914  clientID: xxx
   915  clientSecret: yyy
   916  requestedScopes: ["oidc"]
   917  skipAudienceCheckWhenTokenHasNoAudience: false`, oidcTestServer.URL),
   918  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
   919  		}
   920  
   921  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   922  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   923  		secretConfig := map[string][]byte{
   924  			"tls.crt": utiltest.Cert,
   925  			"tls.key": utiltest.PrivateKey,
   926  		}
   927  
   928  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
   929  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   930  		mgr.verificationDelayNoiseEnabled = false
   931  
   932  		claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   933  		claims.Issuer = oidcTestServer.URL
   934  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   935  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   936  		require.NoError(t, err)
   937  		tokenString, err := token.SignedString(key)
   938  		require.NoError(t, err)
   939  
   940  		_, _, err = mgr.VerifyToken(tokenString)
   941  		require.Error(t, err)
   942  		assert.ErrorIs(t, err, common.ErrTokenVerification)
   943  	})
   944  
   945  	t.Run("OIDC provider is external, audience is client ID, no allowed list specified", func(t *testing.T) {
   946  		config := map[string]string{
   947  			"url": "",
   948  			"oidc.config": fmt.Sprintf(`
   949  name: Test
   950  issuer: %s
   951  clientID: xxx
   952  clientSecret: yyy
   953  requestedScopes: ["oidc"]`, oidcTestServer.URL),
   954  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
   955  		}
   956  
   957  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   958  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   959  		secretConfig := map[string][]byte{
   960  			"tls.crt": utiltest.Cert,
   961  			"tls.key": utiltest.PrivateKey,
   962  		}
   963  
   964  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
   965  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
   966  		mgr.verificationDelayNoiseEnabled = false
   967  
   968  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"xxx"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
   969  		claims.Issuer = oidcTestServer.URL
   970  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
   971  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
   972  		require.NoError(t, err)
   973  		tokenString, err := token.SignedString(key)
   974  		require.NoError(t, err)
   975  
   976  		_, _, err = mgr.VerifyToken(tokenString)
   977  		require.NoError(t, err)
   978  	})
   979  
   980  	t.Run("OIDC provider is external, audience is in allowed list", func(t *testing.T) {
   981  		config := map[string]string{
   982  			"url": "",
   983  			"oidc.config": fmt.Sprintf(`
   984  name: Test
   985  issuer: %s
   986  clientID: xxx
   987  clientSecret: yyy
   988  requestedScopes: ["oidc"]
   989  allowedAudiences:
   990  - something`, oidcTestServer.URL),
   991  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
   992  		}
   993  
   994  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
   995  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
   996  		secretConfig := map[string][]byte{
   997  			"tls.crt": utiltest.Cert,
   998  			"tls.key": utiltest.PrivateKey,
   999  		}
  1000  
  1001  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1002  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1003  		mgr.verificationDelayNoiseEnabled = false
  1004  
  1005  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1006  		claims.Issuer = oidcTestServer.URL
  1007  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1008  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
  1009  		require.NoError(t, err)
  1010  		tokenString, err := token.SignedString(key)
  1011  		require.NoError(t, err)
  1012  
  1013  		_, _, err = mgr.VerifyToken(tokenString)
  1014  		require.NoError(t, err)
  1015  	})
  1016  
  1017  	t.Run("OIDC provider is external, audience is not in allowed list", func(t *testing.T) {
  1018  		config := map[string]string{
  1019  			"url": "",
  1020  			"oidc.config": fmt.Sprintf(`
  1021  name: Test
  1022  issuer: %s
  1023  clientID: xxx
  1024  clientSecret: yyy
  1025  requestedScopes: ["oidc"]
  1026  allowedAudiences:
  1027  - something-else`, oidcTestServer.URL),
  1028  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
  1029  		}
  1030  
  1031  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
  1032  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
  1033  		secretConfig := map[string][]byte{
  1034  			"tls.crt": utiltest.Cert,
  1035  			"tls.key": utiltest.PrivateKey,
  1036  		}
  1037  
  1038  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1039  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1040  		mgr.verificationDelayNoiseEnabled = false
  1041  
  1042  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1043  		claims.Issuer = oidcTestServer.URL
  1044  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1045  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
  1046  		require.NoError(t, err)
  1047  		tokenString, err := token.SignedString(key)
  1048  		require.NoError(t, err)
  1049  
  1050  		_, _, err = mgr.VerifyToken(tokenString)
  1051  		require.Error(t, err)
  1052  		assert.ErrorIs(t, err, common.ErrTokenVerification)
  1053  	})
  1054  
  1055  	t.Run("OIDC provider is external, audience is not client ID, and there is no allow list", func(t *testing.T) {
  1056  		config := map[string]string{
  1057  			"url": "",
  1058  			"oidc.config": fmt.Sprintf(`
  1059  name: Test
  1060  issuer: %s
  1061  clientID: xxx
  1062  clientSecret: yyy
  1063  requestedScopes: ["oidc"]`, oidcTestServer.URL),
  1064  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
  1065  		}
  1066  
  1067  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
  1068  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
  1069  		secretConfig := map[string][]byte{
  1070  			"tls.crt": utiltest.Cert,
  1071  			"tls.key": utiltest.PrivateKey,
  1072  		}
  1073  
  1074  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1075  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1076  		mgr.verificationDelayNoiseEnabled = false
  1077  
  1078  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1079  		claims.Issuer = oidcTestServer.URL
  1080  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1081  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
  1082  		require.NoError(t, err)
  1083  		tokenString, err := token.SignedString(key)
  1084  		require.NoError(t, err)
  1085  
  1086  		_, _, err = mgr.VerifyToken(tokenString)
  1087  		require.Error(t, err)
  1088  		assert.ErrorIs(t, err, common.ErrTokenVerification)
  1089  	})
  1090  
  1091  	t.Run("OIDC provider is external, audience is specified, but allow list is empty", func(t *testing.T) {
  1092  		config := map[string]string{
  1093  			"url": "",
  1094  			"oidc.config": fmt.Sprintf(`
  1095  name: Test
  1096  issuer: %s
  1097  clientID: xxx
  1098  clientSecret: yyy
  1099  requestedScopes: ["oidc"]
  1100  allowedAudiences: []`, oidcTestServer.URL),
  1101  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
  1102  		}
  1103  
  1104  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
  1105  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
  1106  		secretConfig := map[string][]byte{
  1107  			"tls.crt": utiltest.Cert,
  1108  			"tls.key": utiltest.PrivateKey,
  1109  		}
  1110  
  1111  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1112  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1113  		mgr.verificationDelayNoiseEnabled = false
  1114  
  1115  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"something"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1116  		claims.Issuer = oidcTestServer.URL
  1117  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1118  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
  1119  		require.NoError(t, err)
  1120  		tokenString, err := token.SignedString(key)
  1121  		require.NoError(t, err)
  1122  
  1123  		_, _, err = mgr.VerifyToken(tokenString)
  1124  		require.Error(t, err)
  1125  		assert.ErrorIs(t, err, common.ErrTokenVerification)
  1126  	})
  1127  
  1128  	// Make sure the logic works to allow any of the allowed audiences, not just the first one.
  1129  	t.Run("OIDC provider is external, audience is specified, actual audience isn't the first allowed audience", func(t *testing.T) {
  1130  		config := map[string]string{
  1131  			"url": "",
  1132  			"oidc.config": fmt.Sprintf(`
  1133  name: Test
  1134  issuer: %s
  1135  clientID: xxx
  1136  clientSecret: yyy
  1137  requestedScopes: ["oidc"]
  1138  allowedAudiences: ["aud-a", "aud-b"]`, oidcTestServer.URL),
  1139  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
  1140  		}
  1141  
  1142  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
  1143  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
  1144  		secretConfig := map[string][]byte{
  1145  			"tls.crt": utiltest.Cert,
  1146  			"tls.key": utiltest.PrivateKey,
  1147  		}
  1148  
  1149  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1150  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1151  		mgr.verificationDelayNoiseEnabled = false
  1152  
  1153  		claims := jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"aud-b"}, Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1154  		claims.Issuer = oidcTestServer.URL
  1155  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1156  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey)
  1157  		require.NoError(t, err)
  1158  		tokenString, err := token.SignedString(key)
  1159  		require.NoError(t, err)
  1160  
  1161  		_, _, err = mgr.VerifyToken(tokenString)
  1162  		require.NoError(t, err)
  1163  	})
  1164  
  1165  	t.Run("OIDC provider is external, audience is not specified, token is signed with the wrong key", func(t *testing.T) {
  1166  		config := map[string]string{
  1167  			"url": "",
  1168  			"oidc.config": fmt.Sprintf(`
  1169  name: Test
  1170  issuer: %s
  1171  clientID: xxx
  1172  clientSecret: yyy
  1173  requestedScopes: ["oidc"]`, oidcTestServer.URL),
  1174  			"oidc.tls.insecure.skip.verify": "true", // This isn't what we're testing.
  1175  		}
  1176  
  1177  		// This is not actually used in the test. The test only calls the OIDC test server. But a valid cert/key pair
  1178  		// must be set to test VerifyToken's behavior when Argo CD is configured with TLS enabled.
  1179  		secretConfig := map[string][]byte{
  1180  			"tls.crt": utiltest.Cert,
  1181  			"tls.key": utiltest.PrivateKey,
  1182  		}
  1183  
  1184  		settingsMgr := settings.NewSettingsManager(t.Context(), getKubeClientWithConfig(config, secretConfig), "argocd")
  1185  		mgr := NewSessionManager(settingsMgr, getProjLister(), "", nil, NewUserStateStorage(nil))
  1186  		mgr.verificationDelayNoiseEnabled = false
  1187  
  1188  		claims := jwt.RegisteredClaims{Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24))}
  1189  		claims.Issuer = oidcTestServer.URL
  1190  		token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
  1191  		key, err := jwt.ParseRSAPrivateKeyFromPEM(utiltest.PrivateKey2)
  1192  		require.NoError(t, err)
  1193  		tokenString, err := token.SignedString(key)
  1194  		require.NoError(t, err)
  1195  
  1196  		_, _, err = mgr.VerifyToken(tokenString)
  1197  		require.Error(t, err)
  1198  		assert.ErrorIs(t, err, common.ErrTokenVerification)
  1199  	})
  1200  }
  1201  
  1202  func Test_PickFailureAttemptWhenOverflowed(t *testing.T) {
  1203  	t.Run("Not pick admin user from the queue", func(t *testing.T) {
  1204  		failures := map[string]LoginAttempts{
  1205  			"admin": {
  1206  				FailCount: 1,
  1207  			},
  1208  			"test2": {
  1209  				FailCount: 1,
  1210  			},
  1211  		}
  1212  
  1213  		// inside pickRandomNonAdminLoginFailure, it uses random, so we need to test it multiple times
  1214  		for i := 0; i < 1000; i++ {
  1215  			user := pickRandomNonAdminLoginFailure(failures, "test")
  1216  			assert.Equal(t, "test2", *user)
  1217  		}
  1218  	})
  1219  
  1220  	t.Run("Not pick admin user and current user from the queue", func(t *testing.T) {
  1221  		failures := map[string]LoginAttempts{
  1222  			"test": {
  1223  				FailCount: 1,
  1224  			},
  1225  			"admin": {
  1226  				FailCount: 1,
  1227  			},
  1228  			"test2": {
  1229  				FailCount: 1,
  1230  			},
  1231  		}
  1232  
  1233  		// inside pickRandomNonAdminLoginFailure, it uses random, so we need to test it multiple times
  1234  		for i := 0; i < 1000; i++ {
  1235  			user := pickRandomNonAdminLoginFailure(failures, "test")
  1236  			assert.Equal(t, "test2", *user)
  1237  		}
  1238  	})
  1239  }