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  }