github.com/argoproj/argo-cd@v1.8.7/server/account/account.go (about) 1 package account 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "time" 8 9 "github.com/google/uuid" 10 log "github.com/sirupsen/logrus" 11 "golang.org/x/net/context" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/status" 14 "k8s.io/kubernetes/pkg/util/slice" 15 16 "github.com/argoproj/argo-cd/common" 17 "github.com/argoproj/argo-cd/pkg/apiclient/account" 18 "github.com/argoproj/argo-cd/server/rbacpolicy" 19 "github.com/argoproj/argo-cd/util/password" 20 "github.com/argoproj/argo-cd/util/rbac" 21 "github.com/argoproj/argo-cd/util/session" 22 "github.com/argoproj/argo-cd/util/settings" 23 ) 24 25 // Server provides a Session service 26 type Server struct { 27 sessionMgr *session.SessionManager 28 settingsMgr *settings.SettingsManager 29 enf *rbac.Enforcer 30 } 31 32 // NewServer returns a new instance of the Session service 33 func NewServer(sessionMgr *session.SessionManager, settingsMgr *settings.SettingsManager, enf *rbac.Enforcer) *Server { 34 return &Server{sessionMgr, settingsMgr, enf} 35 } 36 37 // UpdatePassword updates the password of the currently authenticated account or the account specified in the request. 38 func (s *Server) UpdatePassword(ctx context.Context, q *account.UpdatePasswordRequest) (*account.UpdatePasswordResponse, error) { 39 issuer := session.Iss(ctx) 40 username := session.Sub(ctx) 41 updatedUsername := username 42 43 if q.Name != "" { 44 updatedUsername = q.Name 45 } 46 // check for permission is user is trying to change someone else's password 47 // assuming user is trying to update someone else if username is different or issuer is not Argo CD 48 if updatedUsername != username || issuer != session.SessionManagerClaimsIssuer { 49 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceAccounts, rbacpolicy.ActionUpdate, q.Name); err != nil { 50 return nil, err 51 } 52 } 53 54 if issuer == session.SessionManagerClaimsIssuer { 55 // local user is changing own password or another user password 56 57 // user is changing own password. 58 // ensure token belongs to a user, not project 59 if q.Name == "" && rbacpolicy.IsProjectSubject(username) { 60 return nil, status.Errorf(codes.InvalidArgument, "password can only be changed for local users, not user %q", username) 61 } 62 63 err := s.sessionMgr.VerifyUsernamePassword(username, q.CurrentPassword) 64 if err != nil { 65 return nil, status.Errorf(codes.InvalidArgument, "current password does not match") 66 } 67 } else { 68 // SSO user is changing or local user password 69 70 iat, err := session.Iat(ctx) 71 if err != nil { 72 return nil, err 73 } 74 if time.Since(iat) > common.ChangePasswordSSOTokenMaxAge { 75 return nil, errors.New("SSO token is too old. Please use 'argocd relogin' to get a new token.") 76 } 77 } 78 79 hashedPassword, err := password.HashPassword(q.NewPassword) 80 if err != nil { 81 return nil, err 82 } 83 84 err = s.settingsMgr.UpdateAccount(updatedUsername, func(acc *settings.Account) error { 85 acc.PasswordHash = hashedPassword 86 now := time.Now().UTC() 87 acc.PasswordMtime = &now 88 return nil 89 }) 90 91 if err != nil { 92 return nil, err 93 } 94 95 if updatedUsername == username { 96 log.Infof("user '%s' updated password", username) 97 } else { 98 log.Infof("user '%s' updated password of user '%s'", username, updatedUsername) 99 } 100 return &account.UpdatePasswordResponse{}, nil 101 102 } 103 104 // CanI checks if the current account has permission to perform an action 105 func (s *Server) CanI(ctx context.Context, r *account.CanIRequest) (*account.CanIResponse, error) { 106 if !slice.ContainsString(rbacpolicy.Actions, r.Action, nil) { 107 return nil, status.Errorf(codes.InvalidArgument, "%v does not contain %s", rbacpolicy.Actions, r.Action) 108 } 109 if !slice.ContainsString(rbacpolicy.Resources, r.Resource, nil) { 110 return nil, status.Errorf(codes.InvalidArgument, "%v does not contain %s", rbacpolicy.Resources, r.Resource) 111 } 112 ok := s.enf.Enforce(ctx.Value("claims"), r.Resource, r.Action, r.Subresource) 113 if ok { 114 return &account.CanIResponse{Value: "yes"}, nil 115 } else { 116 return &account.CanIResponse{Value: "no"}, nil 117 } 118 } 119 120 func toApiAccount(name string, a settings.Account) *account.Account { 121 var capabilities []string 122 for _, c := range a.Capabilities { 123 capabilities = append(capabilities, string(c)) 124 } 125 var tokens []*account.Token 126 for _, t := range a.Tokens { 127 tokens = append(tokens, &account.Token{Id: t.ID, ExpiresAt: t.ExpiresAt, IssuedAt: t.IssuedAt}) 128 } 129 sort.Slice(tokens, func(i, j int) bool { 130 return tokens[i].IssuedAt > tokens[j].IssuedAt 131 }) 132 return &account.Account{ 133 Name: name, 134 Enabled: a.Enabled, 135 Capabilities: capabilities, 136 Tokens: tokens, 137 } 138 } 139 140 func (s *Server) ensureHasAccountPermission(ctx context.Context, action string, account string) error { 141 // account has always has access to itself 142 if session.Sub(ctx) == account && session.Iss(ctx) == session.SessionManagerClaimsIssuer { 143 return nil 144 } 145 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceAccounts, action, account); err != nil { 146 return err 147 } 148 return nil 149 } 150 151 // ListAccounts returns the list of accounts 152 func (s *Server) ListAccounts(ctx context.Context, r *account.ListAccountRequest) (*account.AccountsList, error) { 153 resp := account.AccountsList{} 154 accounts, err := s.settingsMgr.GetAccounts() 155 if err != nil { 156 return nil, err 157 } 158 for name, a := range accounts { 159 if err := s.ensureHasAccountPermission(ctx, rbacpolicy.ActionGet, name); err == nil { 160 resp.Items = append(resp.Items, toApiAccount(name, a)) 161 } 162 } 163 sort.Slice(resp.Items, func(i, j int) bool { 164 return resp.Items[i].Name < resp.Items[j].Name 165 }) 166 return &resp, nil 167 } 168 169 // GetAccount returns an account 170 func (s *Server) GetAccount(ctx context.Context, r *account.GetAccountRequest) (*account.Account, error) { 171 if err := s.ensureHasAccountPermission(ctx, rbacpolicy.ActionGet, r.Name); err != nil { 172 return nil, err 173 } 174 a, err := s.settingsMgr.GetAccount(r.Name) 175 if err != nil { 176 return nil, err 177 } 178 return toApiAccount(r.Name, *a), nil 179 } 180 181 // CreateToken creates a token 182 func (s *Server) CreateToken(ctx context.Context, r *account.CreateTokenRequest) (*account.CreateTokenResponse, error) { 183 if err := s.ensureHasAccountPermission(ctx, rbacpolicy.ActionUpdate, r.Name); err != nil { 184 return nil, err 185 } 186 187 id := r.Id 188 if id == "" { 189 uniqueId, err := uuid.NewRandom() 190 if err != nil { 191 return nil, err 192 } 193 id = uniqueId.String() 194 } 195 196 var tokenString string 197 err := s.settingsMgr.UpdateAccount(r.Name, func(account *settings.Account) error { 198 if account.TokenIndex(id) > -1 { 199 return fmt.Errorf("account already has token with id '%s'", id) 200 } 201 if !account.HasCapability(settings.AccountCapabilityApiKey) { 202 return fmt.Errorf("account '%s' does not have %s capability", r.Name, settings.AccountCapabilityApiKey) 203 } 204 205 now := time.Now() 206 var err error 207 tokenString, err = s.sessionMgr.Create(r.Name, r.ExpiresIn, id) 208 if err != nil { 209 return err 210 } 211 212 var expiresAt int64 213 if r.ExpiresIn > 0 { 214 expiresAt = now.Add(time.Duration(r.ExpiresIn) * time.Second).Unix() 215 } 216 account.Tokens = append(account.Tokens, settings.Token{ 217 ID: id, 218 IssuedAt: now.Unix(), 219 ExpiresAt: expiresAt, 220 }) 221 return nil 222 }) 223 if err != nil { 224 return nil, err 225 } 226 return &account.CreateTokenResponse{Token: tokenString}, nil 227 } 228 229 // DeleteToken deletes a token 230 func (s *Server) DeleteToken(ctx context.Context, r *account.DeleteTokenRequest) (*account.EmptyResponse, error) { 231 if err := s.ensureHasAccountPermission(ctx, rbacpolicy.ActionUpdate, r.Name); err != nil { 232 return nil, err 233 } 234 235 err := s.settingsMgr.UpdateAccount(r.Name, func(account *settings.Account) error { 236 if index := account.TokenIndex(r.Id); index > -1 { 237 account.Tokens = append(account.Tokens[:index], account.Tokens[index+1:]...) 238 return nil 239 } 240 return status.Errorf(codes.NotFound, "token with id '%s' does not exist", r.Id) 241 }) 242 if err != nil { 243 return nil, err 244 } 245 return &account.EmptyResponse{}, nil 246 }