github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/user.go (about) 1 /* 2 * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "fmt" 9 "net/http" 10 "net/url" 11 "time" 12 13 "github.com/vmware/go-vcloud-director/v2/types/v56" 14 "github.com/vmware/go-vcloud-director/v2/util" 15 ) 16 17 // Definition of an OrgUser 18 type OrgUser struct { 19 User *types.User 20 client *Client 21 AdminOrg *AdminOrg // needed to be able to update, as the list of roles is found in the Org 22 } 23 24 // Simplified structure to insert or modify an organization user 25 type OrgUserConfiguration struct { 26 Name string // Mandatory 27 Password string // Mandatory 28 RoleName string // Mandatory 29 ProviderType string // Optional: defaults to "INTEGRATED" 30 IsEnabled bool // Optional: defaults to false 31 IsLocked bool // Only used for updates 32 IsExternal bool // Optional: defaults to false 33 DeployedVmQuota int // Optional: 0 means "unlimited" 34 StoredVmQuota int // Optional: 0 means "unlimited" 35 FullName string // Optional 36 Description string // Optional 37 EmailAddress string // Optional 38 Telephone string // Optional 39 IM string // Optional 40 } 41 42 const ( 43 // Common role names and provider types are kept here to reduce hard-coded text and prevent mistakes 44 // Roles that are added to the organization need to be entered as free text 45 46 OrgUserRoleOrganizationAdministrator = "Organization Administrator" 47 OrgUserRoleCatalogAuthor = "Catalog Author" 48 OrgUserRoleVappAuthor = "vApp Author" 49 OrgUserRoleVappUser = "vApp User" 50 OrgUserRoleConsoleAccessOnly = "Console Access Only" 51 OrgUserRoleDeferToIdentityProvider = "Defer to Identity Provider" 52 53 // Allowed values for provider types 54 OrgUserProviderIntegrated = "INTEGRATED" // The user is created locally or imported from LDAP 55 OrgUserProviderSAML = "SAML" // The user is imported from a SAML identity provider. 56 OrgUserProviderOAUTH = "OAUTH" // The user is imported from an OAUTH identity provider 57 ) 58 59 // Used to check the validity of provider type on creation 60 var OrgUserProviderTypes = []string{ 61 OrgUserProviderIntegrated, 62 OrgUserProviderSAML, 63 OrgUserProviderOAUTH, 64 } 65 66 // NewUser creates an empty user 67 func NewUser(cli *Client, org *AdminOrg) *OrgUser { 68 return &OrgUser{ 69 User: new(types.User), 70 client: cli, 71 AdminOrg: org, 72 } 73 } 74 75 // FetchUserByHref returns a user by its HREF 76 // Deprecated: use GetUserByHref instead 77 func (adminOrg *AdminOrg) FetchUserByHref(href string) (*OrgUser, error) { 78 return adminOrg.GetUserByHref(href) 79 } 80 81 // FetchUserByName returns a user by its Name 82 // Deprecated: use GetUserByName instead 83 func (adminOrg *AdminOrg) FetchUserByName(name string, refresh bool) (*OrgUser, error) { 84 return adminOrg.GetUserByName(name, refresh) 85 } 86 87 // FetchUserById returns a user by its ID 88 // Deprecated: use GetUserById instead 89 func (adminOrg *AdminOrg) FetchUserById(id string, refresh bool) (*OrgUser, error) { 90 return adminOrg.GetUserById(id, refresh) 91 } 92 93 // FetchUserById returns a user by its Name or ID 94 // Deprecated: use GetUserByNameOrId instead 95 func (adminOrg *AdminOrg) FetchUserByNameOrId(identifier string, refresh bool) (*OrgUser, error) { 96 return adminOrg.GetUserByNameOrId(identifier, refresh) 97 } 98 99 // GetUserByHref returns a user by its HREF, without need for 100 // searching in the adminOrg user list 101 func (adminOrg *AdminOrg) GetUserByHref(href string) (*OrgUser, error) { 102 orgUser := NewUser(adminOrg.client, adminOrg) 103 104 _, err := adminOrg.client.ExecuteRequest(href, http.MethodGet, 105 types.MimeAdminUser, "error getting user: %s", nil, orgUser.User) 106 107 if err != nil { 108 return nil, err 109 } 110 return orgUser, nil 111 } 112 113 // GetUserByName retrieves a user within an admin organization by name 114 // Returns a valid user if it exists. If it doesn't, returns nil and ErrorEntityNotFound 115 // If argument refresh is true, the AdminOrg will be refreshed before searching. 116 // This is usually done after creating, modifying, or deleting users. 117 // If it is false, it will search within the data already in memory (useful when 118 // looping through the users and we know that no changes have occurred in the meantime) 119 func (adminOrg *AdminOrg) GetUserByName(name string, refresh bool) (*OrgUser, error) { 120 if refresh { 121 err := adminOrg.Refresh() 122 if err != nil { 123 return nil, err 124 } 125 } 126 127 for _, user := range adminOrg.AdminOrg.Users.User { 128 if user.Name == name { 129 return adminOrg.GetUserByHref(user.HREF) 130 } 131 } 132 return nil, ErrorEntityNotFound 133 } 134 135 // GetUserById retrieves a user within an admin organization by ID 136 // Returns a valid user if it exists. If it doesn't, returns nil and ErrorEntityNotFound 137 // If argument refresh is true, the AdminOrg will be refreshed before searching. 138 // This is usually done after creating, modifying, or deleting users. 139 // If it is false, it will search within the data already in memory (useful when 140 // looping through the users and we know that no changes have occurred in the meantime) 141 func (adminOrg *AdminOrg) GetUserById(id string, refresh bool) (*OrgUser, error) { 142 if refresh { 143 err := adminOrg.Refresh() 144 if err != nil { 145 return nil, err 146 } 147 } 148 149 for _, user := range adminOrg.AdminOrg.Users.User { 150 if equalIds(id, user.ID, user.HREF) { 151 return adminOrg.GetUserByHref(user.HREF) 152 } 153 } 154 return nil, ErrorEntityNotFound 155 } 156 157 // GetUserByNameOrId retrieves a user within an admin organization 158 // by either name or ID 159 // Returns a valid user if it exists. If it doesn't, returns nil and ErrorEntityNotFound 160 // If argument refresh is true, the AdminOrg will be refreshed before searching. 161 // This is usually done after creating, modifying, or deleting users. 162 // If it is false, it will search within the data already in memory (useful when 163 // looping through the users and we know that no changes have occurred in the meantime) 164 func (adminOrg *AdminOrg) GetUserByNameOrId(identifier string, refresh bool) (*OrgUser, error) { 165 getByName := func(name string, refresh bool) (interface{}, error) { return adminOrg.GetUserByName(name, refresh) } 166 getById := func(name string, refresh bool) (interface{}, error) { return adminOrg.GetUserById(name, refresh) } 167 entity, err := getEntityByNameOrId(getByName, getById, identifier, refresh) 168 if entity == nil { 169 return nil, err 170 } 171 return entity.(*OrgUser), err 172 } 173 174 // GetRoleReference finds a role within the organization 175 func (adminOrg *AdminOrg) GetRoleReference(roleName string) (*types.Reference, error) { 176 177 // We force refresh of the organization, to make sure that roles recently created 178 // are taken into account. 179 // This will become unnecessary when we refactor the User management with OpenAPI 180 err := adminOrg.Refresh() 181 if err != nil { 182 return nil, err 183 } 184 for _, role := range adminOrg.AdminOrg.RoleReferences.RoleReference { 185 if role.Name == roleName { 186 return role, nil 187 } 188 } 189 190 return nil, ErrorEntityNotFound 191 } 192 193 // Retrieves a user within the boundaries of MaxRetryTimeout 194 func retrieveUserWithTimeout(adminOrg *AdminOrg, userName string) (*OrgUser, error) { 195 196 // Attempting to retrieve the user 197 delayPerAttempt := 200 * time.Millisecond 198 maxOperationTimeout := time.Duration(adminOrg.client.MaxRetryTimeout) * time.Second 199 200 // We make sure that the timeout is never less than 2 seconds 201 if maxOperationTimeout < 2*time.Second { 202 maxOperationTimeout = 2 * time.Second 203 } 204 205 // If maxRetryTimeout is set to a higher limit, we lower it to match the 206 // expectations for this operation. If the user is not created within 10 seconds, 207 // there is no need to wait for more. Usually, the operation lasts between 200ms and 900ms 208 if maxOperationTimeout > 10*time.Second { 209 maxOperationTimeout = 10 * time.Second 210 } 211 212 startTime := time.Now() 213 elapsed := time.Since(startTime) 214 var newUser *OrgUser 215 var err error 216 for elapsed < maxOperationTimeout { 217 newUser, err = adminOrg.GetUserByName(userName, true) 218 if err == nil { 219 break 220 } 221 time.Sleep(delayPerAttempt) 222 elapsed = time.Since(startTime) 223 } 224 225 elapsed = time.Since(startTime) 226 227 // If the user was not retrieved within the allocated time, we inform the user about the failure 228 // and the time it occurred to get to this point, so that they may try with a longer time 229 if err != nil { 230 return nil, fmt.Errorf("failure to retrieve a new user after %s : %s", elapsed, err) 231 } 232 233 return newUser, nil 234 } 235 236 // CreateUser creates an OrgUser from a full configuration structure 237 // The timeOut variable is the maximum time we wait for the user to be ready 238 // (This operation does not return a task) 239 // This function returns as soon as the user has been created, which could be as 240 // little as 200ms or as much as Client.MaxRetryTimeout 241 // Mandatory fields are: Name, Role, Password. 242 // https://code.vmware.com/apis/442/vcloud-director#/doc/doc/operations/POST-CreateUser.html 243 func (adminOrg *AdminOrg) CreateUser(userConfiguration *types.User) (*OrgUser, error) { 244 err := validateUserForCreation(userConfiguration) 245 if err != nil { 246 return nil, err 247 } 248 249 userCreateHREF, err := url.ParseRequestURI(adminOrg.AdminOrg.HREF) 250 if err != nil { 251 return nil, fmt.Errorf("error parsing admin org url: %s", err) 252 } 253 userCreateHREF.Path += "/users" 254 255 user := NewUser(adminOrg.client, adminOrg) 256 257 _, err = adminOrg.client.ExecuteRequest(userCreateHREF.String(), http.MethodPost, 258 types.MimeAdminUser, "error creating user: %s", userConfiguration, user.User) 259 if err != nil { 260 return nil, err 261 } 262 263 // If there is a valid task, we try to follow through 264 // A valid task exists if the Task object in the user structure 265 // is not nil and contains at least a task 266 if user.User.Tasks != nil && len(user.User.Tasks.Task) > 0 { 267 task := NewTask(adminOrg.client) 268 task.Task = user.User.Tasks.Task[0] 269 err = task.WaitTaskCompletion() 270 271 if err != nil { 272 return nil, err 273 } 274 } 275 276 return retrieveUserWithTimeout(adminOrg, userConfiguration.Name) 277 } 278 279 // CreateUserSimple creates an org user from a simplified structure 280 func (adminOrg *AdminOrg) CreateUserSimple(userData OrgUserConfiguration) (*OrgUser, error) { 281 282 if userData.Name == "" { 283 return nil, fmt.Errorf("name is mandatory to create a user") 284 } 285 if userData.Password == "" && !userData.IsExternal { 286 return nil, fmt.Errorf("password is mandatory to create a user") 287 } 288 if userData.Password != "" && userData.IsExternal { 289 // External users don't need to provide a password 290 userData.Password = "" 291 } 292 293 if userData.RoleName == "" { 294 return nil, fmt.Errorf("role is mandatory to create a user") 295 } 296 role, err := adminOrg.GetRoleReference(userData.RoleName) 297 if err != nil { 298 return nil, fmt.Errorf("error finding a role named %s", userData.RoleName) 299 } 300 301 var userConfiguration = types.User{ 302 Xmlns: types.XMLNamespaceVCloud, 303 Type: types.MimeAdminUser, 304 ProviderType: userData.ProviderType, 305 Name: userData.Name, 306 IsEnabled: userData.IsEnabled, 307 IsExternal: userData.IsExternal, 308 Password: userData.Password, 309 DeployedVmQuota: userData.DeployedVmQuota, 310 StoredVmQuota: userData.StoredVmQuota, 311 FullName: userData.FullName, 312 EmailAddress: userData.EmailAddress, 313 Description: userData.Description, 314 Telephone: userData.Telephone, 315 IM: userData.IM, 316 Role: &types.Reference{HREF: role.HREF}, 317 } 318 319 // ShowUser(userConfiguration) 320 return adminOrg.CreateUser(&userConfiguration) 321 } 322 323 // GetRoleName retrieves the name of the role currently assigned to the user 324 func (user *OrgUser) GetRoleName() string { 325 if user.User.Role == nil { 326 return "" 327 } 328 return user.User.Role.Name 329 } 330 331 // Delete removes the user, returning an error if the call fails. 332 // if requested, it will attempt to take ownership before the removal. 333 // API Documentation: https://code.vmware.com/apis/442/vcloud-director#/doc/doc/operations/DELETE-User.html 334 // Note: in the GUI we need to disable the user before deleting. 335 // There is no such constraint with the API. 336 // 337 // Expected behaviour: 338 // with takeOwnership = true, all entities owned by the user being deleted will be transferred to the caller. 339 // with takeOwnership = false, if the user own catalogs, networks, or running VMs/vApps, the call will fail. 340 // 341 // If the user owns only powered-off VMs/vApps, the call will succeeds and the 342 // VMs/vApps will be removed. 343 func (user *OrgUser) Delete(takeOwnership bool) error { 344 util.Logger.Printf("[TRACE] Deleting user: %#v (take ownership: %v)", user.User.Name, takeOwnership) 345 346 if takeOwnership { 347 err := user.TakeOwnership() 348 if err != nil { 349 return err 350 } 351 } 352 353 userHREF, err := url.ParseRequestURI(user.User.Href) 354 if err != nil { 355 return fmt.Errorf("error getting HREF for user %s : %s", user.User.Name, err) 356 } 357 util.Logger.Printf("[TRACE] Url for deleting user : %#v and name: %s", userHREF, user.User.Name) 358 359 return user.client.ExecuteRequestWithoutResponse(userHREF.String(), http.MethodDelete, 360 types.MimeAdminUser, "error deleting user : %s", nil) 361 } 362 363 // UpdateSimple updates the user, using ALL the fields in userData structure 364 // returning an error if the call fails. 365 // Careful: DeployedVmQuota and StoredVmQuota use a `0` value to mean "unlimited" 366 func (user *OrgUser) UpdateSimple(userData OrgUserConfiguration) error { 367 util.Logger.Printf("[TRACE] Updating user: %#v", user.User.Name) 368 369 if userData.Name != "" { 370 user.User.Name = userData.Name 371 } 372 if userData.ProviderType != "" { 373 user.User.ProviderType = userData.ProviderType 374 } 375 if userData.Description != "" { 376 user.User.Description = userData.Description 377 } 378 if userData.FullName != "" { 379 user.User.FullName = userData.FullName 380 } 381 if userData.EmailAddress != "" { 382 user.User.EmailAddress = userData.EmailAddress 383 } 384 if userData.Telephone != "" { 385 user.User.Telephone = userData.Telephone 386 } 387 if userData.Password != "" { 388 user.User.Password = userData.Password 389 } 390 user.User.StoredVmQuota = userData.StoredVmQuota 391 user.User.DeployedVmQuota = userData.DeployedVmQuota 392 user.User.IsEnabled = userData.IsEnabled 393 user.User.IsLocked = userData.IsLocked 394 user.User.IsExternal = userData.IsExternal 395 396 if userData.RoleName != "" && user.User.Role != nil && user.User.Role.Name != userData.RoleName { 397 newRole, err := user.AdminOrg.GetRoleReference(userData.RoleName) 398 if err != nil { 399 return err 400 } 401 user.User.Role = newRole 402 } 403 return user.Update() 404 } 405 406 // Update updates the user, using its own configuration data 407 // returning an error if the call fails. 408 // API Documentation: https://code.vmware.com/apis/442/vcloud-director#/doc/doc/operations/PUT-User.html 409 func (user *OrgUser) Update() error { 410 util.Logger.Printf("[TRACE] Updating user: %s", user.User.Name) 411 412 // Makes sure that GroupReferences is either properly filled or nil, 413 // because otherwise vCD will complain that the payload is not well formatted when 414 // the configuration contains a non-empty password. 415 if user.User.GroupReferences != nil { 416 if len(user.User.GroupReferences.GroupReference) == 0 { 417 user.User.GroupReferences = nil 418 } 419 } 420 421 userHREF, err := url.ParseRequestURI(user.User.Href) 422 if err != nil { 423 return fmt.Errorf("error getting HREF for user %s : %s", user.User.Name, err) 424 } 425 util.Logger.Printf("[TRACE] Url for updating user : %#v and name: %s", userHREF, user.User.Name) 426 427 _, err = user.client.ExecuteRequest(userHREF.String(), http.MethodPut, 428 types.MimeAdminUser, "error updating user : %s", user.User, nil) 429 return err 430 } 431 432 // Disable disables a user, if it is enabled. Fails otherwise. 433 func (user *OrgUser) Disable() error { 434 util.Logger.Printf("[TRACE] Disabling user: %s", user.User.Name) 435 436 if !user.User.IsEnabled { 437 return fmt.Errorf("user %s is already disabled", user.User.Name) 438 } 439 user.User.IsEnabled = false 440 441 return user.Update() 442 } 443 444 // ChangePassword changes user's password 445 // Constraints: the password must be non-empty, with a minimum of 6 characters 446 func (user *OrgUser) ChangePassword(newPass string) error { 447 util.Logger.Printf("[TRACE] Changing user's password user: %s", user.User.Name) 448 449 user.User.Password = newPass 450 451 return user.Update() 452 } 453 454 // Enable enables a user if it was disabled. Fails otherwise. 455 func (user *OrgUser) Enable() error { 456 util.Logger.Printf("[TRACE] Enabling user: %s", user.User.Name) 457 458 if user.User.IsEnabled { 459 return fmt.Errorf("user %s is already enabled", user.User.Name) 460 } 461 user.User.IsEnabled = true 462 463 return user.Update() 464 } 465 466 // Unlock unlocks a user that was locked out by the system. 467 // Note that there is no procedure to LOCK a user: it is locked by the system when it exceeds the number of 468 // unauthorized access attempts 469 func (user *OrgUser) Unlock() error { 470 util.Logger.Printf("[TRACE] Unlocking user: %s", user.User.Name) 471 472 if !user.User.IsLocked { 473 return fmt.Errorf("user %s is not locked", user.User.Name) 474 } 475 user.User.IsLocked = false 476 477 return user.Update() 478 } 479 480 // ChangeRole changes a user's role 481 // Fails is we try to set the same role as the current one. 482 // Also fails if the provided role name is not found. 483 func (user *OrgUser) ChangeRole(roleName string) error { 484 util.Logger.Printf("[TRACE] Changing user's role: %s", user.User.Name) 485 486 if roleName == "" { 487 return fmt.Errorf("role name cannot be empty") 488 } 489 490 if user.User.Role != nil && user.User.Role.Name == roleName { 491 return fmt.Errorf("new role is the same as current role") 492 } 493 494 newRole, err := user.AdminOrg.GetRoleReference(roleName) 495 if err != nil { 496 return err 497 } 498 user.User.Role = newRole 499 500 return user.Update() 501 } 502 503 // TakeOwnership takes ownership of the user's objects. 504 // Ownership is transferred to the caller. 505 // This is a call to make before deleting. Calling user.DeleteTakeOwnership() will 506 // run TakeOwnership before the actual user removal. 507 // API Documentation: https://code.vmware.com/apis/442/vcloud-director#/doc/doc/operations/POST-TakeOwnership.html 508 func (user *OrgUser) TakeOwnership() error { 509 util.Logger.Printf("[TRACE] Taking ownership from user: %s", user.User.Name) 510 511 userHREF, err := url.ParseRequestURI(user.User.Href + "/action/takeOwnership") 512 if err != nil { 513 return fmt.Errorf("error getting HREF for user %s : %s", user.User.Name, err) 514 } 515 util.Logger.Printf("[TRACE] Url for taking ownership from user : %#v and name: %s", userHREF, user.User.Name) 516 517 return user.client.ExecuteRequestWithoutResponse(userHREF.String(), http.MethodPost, 518 types.MimeAdminUser, "error taking ownership from user : %s", nil) 519 } 520 521 // validateUserForInput makes sure that the minimum data 522 // needed for creating an org user has been included in the configuration 523 func validateUserForCreation(user *types.User) error { 524 var missingField = "missing field %s" 525 if user.Xmlns == "" { 526 user.Xmlns = types.XMLNamespaceVCloud 527 } 528 if user.Type == "" { 529 user.Type = types.MimeAdminUser 530 } 531 if user.Name == "" { 532 return fmt.Errorf(missingField, "Name") 533 } 534 if user.Password == "" && !user.IsExternal { 535 return fmt.Errorf(missingField, "Password") 536 } 537 if user.Password != "" && user.IsExternal { 538 // External users don't need to provide a password 539 user.Password = "" 540 } 541 if user.ProviderType != "" { 542 validProviderType := false 543 for _, pt := range OrgUserProviderTypes { 544 if user.ProviderType == pt { 545 validProviderType = true 546 } 547 } 548 if !validProviderType { 549 return fmt.Errorf("'%s' is not a valid provider type", user.ProviderType) 550 } 551 } 552 if user.Role.HREF == "" { 553 return fmt.Errorf(missingField, "Role.HREF") 554 } 555 return nil 556 }