github.com/argoproj/argo-cd@v1.8.7/server/account/account_test.go (about) 1 package account 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/dgrijalva/jwt-go/v4" 9 "github.com/stretchr/testify/assert" 10 "google.golang.org/grpc/codes" 11 "google.golang.org/grpc/status" 12 v1 "k8s.io/api/core/v1" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/client-go/kubernetes/fake" 15 16 "github.com/argoproj/argo-cd/common" 17 "github.com/argoproj/argo-cd/pkg/apiclient/account" 18 sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session" 19 "github.com/argoproj/argo-cd/server/session" 20 "github.com/argoproj/argo-cd/test" 21 "github.com/argoproj/argo-cd/util/errors" 22 "github.com/argoproj/argo-cd/util/password" 23 "github.com/argoproj/argo-cd/util/rbac" 24 sessionutil "github.com/argoproj/argo-cd/util/session" 25 "github.com/argoproj/argo-cd/util/settings" 26 ) 27 28 const ( 29 testNamespace = "default" 30 ) 31 32 // return an AccountServer which returns fake data 33 func newTestAccountServer(ctx context.Context, opts ...func(cm *v1.ConfigMap, secret *v1.Secret)) (*Server, *session.Server) { 34 return newTestAccountServerExt(ctx, func(claims jwt.Claims, rvals ...interface{}) bool { 35 return true 36 }, opts...) 37 } 38 39 func newTestAccountServerExt(ctx context.Context, enforceFn rbac.ClaimsEnforcerFunc, opts ...func(cm *v1.ConfigMap, secret *v1.Secret)) (*Server, *session.Server) { 40 bcrypt, err := password.HashPassword("oldpassword") 41 errors.CheckError(err) 42 cm := &v1.ConfigMap{ 43 ObjectMeta: metav1.ObjectMeta{ 44 Name: "argocd-cm", 45 Namespace: testNamespace, 46 Labels: map[string]string{ 47 "app.kubernetes.io/part-of": "argocd", 48 }, 49 }, 50 Data: map[string]string{}, 51 } 52 secret := &v1.Secret{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: "argocd-secret", 55 Namespace: testNamespace, 56 }, 57 Data: map[string][]byte{ 58 "admin.password": []byte(bcrypt), 59 "server.secretkey": []byte("test"), 60 }, 61 } 62 for i := range opts { 63 opts[i](cm, secret) 64 } 65 kubeclientset := fake.NewSimpleClientset(cm, secret) 66 settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace) 67 sessionMgr := sessionutil.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", sessionutil.NewInMemoryUserStateStorage()) 68 enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) 69 enforcer.SetClaimsEnforcerFunc(enforceFn) 70 71 return NewServer(sessionMgr, settingsMgr, enforcer), session.NewServer(sessionMgr, nil, nil, nil) 72 } 73 74 func getAdminAccount(mgr *settings.SettingsManager) (*settings.Account, error) { 75 accounts, err := mgr.GetAccounts() 76 if err != nil { 77 return nil, err 78 } 79 adminAccount := accounts[common.ArgoCDAdminUsername] 80 return &adminAccount, nil 81 } 82 83 func adminContext(ctx context.Context) context.Context { 84 // nolint:staticcheck 85 return context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin", Issuer: sessionutil.SessionManagerClaimsIssuer}) 86 } 87 88 func ssoAdminContext(ctx context.Context, iat time.Time) context.Context { 89 // nolint:staticcheck 90 return context.WithValue(ctx, "claims", &jwt.StandardClaims{ 91 Subject: "admin", 92 Issuer: "https://myargocdhost.com/api/dex", 93 IssuedAt: jwt.At(iat), 94 }) 95 } 96 97 func projTokenContext(ctx context.Context) context.Context { 98 // nolint:staticcheck 99 return context.WithValue(ctx, "claims", &jwt.StandardClaims{ 100 Subject: "proj:demo:deployer", 101 Issuer: sessionutil.SessionManagerClaimsIssuer, 102 }) 103 } 104 105 func TestUpdatePassword(t *testing.T) { 106 accountServer, sessionServer := newTestAccountServer(context.Background()) 107 ctx := adminContext(context.Background()) 108 var err error 109 110 // ensure password is not allowed to be updated if given bad password 111 _, err = accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "badpassword", NewPassword: "newpassword"}) 112 assert.Error(t, err) 113 assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword")) 114 assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword")) 115 // verify old password works 116 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "oldpassword"}) 117 assert.NoError(t, err) 118 // verify new password doesn't 119 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "newpassword"}) 120 assert.Error(t, err) 121 122 // ensure password can be updated with valid password and immediately be used 123 adminAccount, err := getAdminAccount(accountServer.settingsMgr) 124 assert.NoError(t, err) 125 prevHash := adminAccount.PasswordHash 126 _, err = accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword"}) 127 assert.NoError(t, err) 128 adminAccount, err = getAdminAccount(accountServer.settingsMgr) 129 assert.NoError(t, err) 130 assert.NotEqual(t, prevHash, adminAccount.PasswordHash) 131 assert.NoError(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "newpassword")) 132 assert.Error(t, accountServer.sessionMgr.VerifyUsernamePassword("admin", "oldpassword")) 133 // verify old password is invalid 134 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "oldpassword"}) 135 assert.Error(t, err) 136 // verify new password works 137 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "admin", Password: "newpassword"}) 138 assert.NoError(t, err) 139 } 140 141 func TestUpdatePassword_AdminUpdatesAnotherUser(t *testing.T) { 142 accountServer, sessionServer := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) { 143 cm.Data["accounts.anotherUser"] = "login" 144 }) 145 ctx := adminContext(context.Background()) 146 147 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"}) 148 assert.NoError(t, err) 149 150 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "anotherUser", Password: "newpassword"}) 151 assert.NoError(t, err) 152 } 153 154 func TestUpdatePassword_DoesNotHavePermissions(t *testing.T) { 155 enforcer := func(claims jwt.Claims, rvals ...interface{}) bool { 156 return false 157 } 158 159 t.Run("LocalAccountUpdatesAnotherAccount", func(t *testing.T) { 160 accountServer, _ := newTestAccountServerExt(context.Background(), enforcer, func(cm *v1.ConfigMap, secret *v1.Secret) { 161 cm.Data["accounts.anotherUser"] = "login" 162 }) 163 ctx := adminContext(context.Background()) 164 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"}) 165 assert.Error(t, err) 166 assert.Contains(t, err.Error(), "permission denied") 167 }) 168 169 t.Run("SSOAccountWithTheSameName", func(t *testing.T) { 170 accountServer, _ := newTestAccountServerExt(context.Background(), enforcer) 171 ctx := ssoAdminContext(context.Background(), time.Now()) 172 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "admin"}) 173 assert.Error(t, err) 174 assert.Contains(t, err.Error(), "permission denied") 175 }) 176 } 177 178 func TestUpdatePassword_ProjectToken(t *testing.T) { 179 accountServer, _ := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) { 180 cm.Data["accounts.anotherUser"] = "login" 181 }) 182 ctx := projTokenContext(context.Background()) 183 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword"}) 184 assert.Error(t, err) 185 assert.Contains(t, err.Error(), "password can only be changed for local users") 186 } 187 188 func TestUpdatePassword_OldSSOToken(t *testing.T) { 189 accountServer, _ := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) { 190 cm.Data["accounts.anotherUser"] = "login" 191 }) 192 ctx := ssoAdminContext(context.Background(), time.Now().Add(-2*common.ChangePasswordSSOTokenMaxAge)) 193 194 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"}) 195 assert.Error(t, err) 196 } 197 198 func TestUpdatePassword_SSOUserUpdatesAnotherUser(t *testing.T) { 199 accountServer, sessionServer := newTestAccountServer(context.Background(), func(cm *v1.ConfigMap, secret *v1.Secret) { 200 cm.Data["accounts.anotherUser"] = "login" 201 }) 202 ctx := ssoAdminContext(context.Background(), time.Now()) 203 204 _, err := accountServer.UpdatePassword(ctx, &account.UpdatePasswordRequest{CurrentPassword: "oldpassword", NewPassword: "newpassword", Name: "anotherUser"}) 205 assert.NoError(t, err) 206 207 _, err = sessionServer.Create(ctx, &sessionpkg.SessionCreateRequest{Username: "anotherUser", Password: "newpassword"}) 208 assert.NoError(t, err) 209 } 210 211 func TestListAccounts_NoAccountsConfigured(t *testing.T) { 212 ctx := adminContext(context.Background()) 213 214 accountServer, _ := newTestAccountServer(ctx) 215 resp, err := accountServer.ListAccounts(ctx, &account.ListAccountRequest{}) 216 assert.NoError(t, err) 217 assert.Len(t, resp.Items, 1) 218 } 219 220 func TestListAccounts_AccountsAreConfigured(t *testing.T) { 221 ctx := adminContext(context.Background()) 222 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 223 cm.Data["accounts.account1"] = "apiKey" 224 cm.Data["accounts.account2"] = "login, apiKey" 225 cm.Data["accounts.account2.enabled"] = "false" 226 }) 227 228 resp, err := accountServer.ListAccounts(ctx, &account.ListAccountRequest{}) 229 assert.NoError(t, err) 230 assert.Len(t, resp.Items, 3) 231 assert.ElementsMatch(t, []*account.Account{ 232 {Name: "admin", Capabilities: []string{"login"}, Enabled: true}, 233 {Name: "account1", Capabilities: []string{"apiKey"}, Enabled: true}, 234 {Name: "account2", Capabilities: []string{"login", "apiKey"}, Enabled: false}, 235 }, resp.Items) 236 } 237 238 func TestGetAccount(t *testing.T) { 239 ctx := adminContext(context.Background()) 240 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 241 cm.Data["accounts.account1"] = "apiKey" 242 }) 243 244 t.Run("ExistingAccount", func(t *testing.T) { 245 acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"}) 246 assert.NoError(t, err) 247 248 assert.Equal(t, acc.Name, "account1") 249 }) 250 251 t.Run("NonExistingAccount", func(t *testing.T) { 252 _, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "bad-name"}) 253 assert.Error(t, err) 254 assert.Equal(t, status.Code(err), codes.NotFound) 255 }) 256 } 257 258 func TestCreateToken_SuccessfullyCreated(t *testing.T) { 259 ctx := adminContext(context.Background()) 260 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 261 cm.Data["accounts.account1"] = "apiKey" 262 }) 263 264 _, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1"}) 265 assert.NoError(t, err) 266 267 acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"}) 268 assert.NoError(t, err) 269 270 assert.Len(t, acc.Tokens, 1) 271 } 272 273 func TestCreateToken_DoesNotHaveCapability(t *testing.T) { 274 ctx := adminContext(context.Background()) 275 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 276 cm.Data["accounts.account1"] = "login" 277 }) 278 279 _, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1"}) 280 assert.Error(t, err) 281 } 282 283 func TestCreateToken_UserSpecifiedID(t *testing.T) { 284 ctx := adminContext(context.Background()) 285 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 286 cm.Data["accounts.account1"] = "apiKey" 287 }) 288 289 _, err := accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1", Id: "test"}) 290 assert.NoError(t, err) 291 292 _, err = accountServer.CreateToken(ctx, &account.CreateTokenRequest{Name: "account1", Id: "test"}) 293 if !assert.Error(t, err) { 294 return 295 } 296 assert.Contains(t, "account already has token with id 'test'", err.Error()) 297 } 298 299 func TestDeleteToken_SuccessfullyRemoved(t *testing.T) { 300 ctx := adminContext(context.Background()) 301 accountServer, _ := newTestAccountServer(ctx, func(cm *v1.ConfigMap, secret *v1.Secret) { 302 cm.Data["accounts.account1"] = "apiKey" 303 secret.Data["accounts.account1.tokens"] = []byte(`[{"id":"123","iat":1583789194,"exp":1583789194}]`) 304 }) 305 306 _, err := accountServer.DeleteToken(ctx, &account.DeleteTokenRequest{Name: "account1", Id: "123"}) 307 assert.NoError(t, err) 308 309 acc, err := accountServer.GetAccount(ctx, &account.GetAccountRequest{Name: "account1"}) 310 assert.NoError(t, err) 311 312 assert.Len(t, acc.Tokens, 0) 313 }