github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/service_account.go (about)

     1  /*
     2   * Copyright 2023 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  package govcd
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    12  )
    13  
    14  type ServiceAccount struct {
    15  	ServiceAccount *types.ServiceAccount
    16  	authParams     *types.ServiceAccountAuthParams
    17  	org            *Org
    18  }
    19  
    20  // GetServiceAccountById gets a Service Account by its ID
    21  func (org *Org) GetServiceAccountById(serviceAccountId string) (*ServiceAccount, error) {
    22  	client := org.client
    23  
    24  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts
    25  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, serviceAccountId)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	newServiceAccount := &ServiceAccount{
    36  		ServiceAccount: &types.ServiceAccount{},
    37  		org:            org,
    38  	}
    39  
    40  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, newServiceAccount.ServiceAccount, nil)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	return newServiceAccount, nil
    46  }
    47  
    48  // GetAllServiceAccounts gets all service accounts with the specified query parameters
    49  func (org *Org) GetAllServiceAccounts(queryParams url.Values) ([]*ServiceAccount, error) {
    50  	client := org.client
    51  
    52  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts
    53  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	tenantContext, err := org.getTenantContext()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	// VCD has a pageSize limit on this specific endpoint
    69  	queryParams.Add("pageSize", "32")
    70  	typeResponses := []*types.ServiceAccount{{}}
    71  	err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &typeResponses, getTenantContextHeader(tenantContext))
    72  
    73  	if err != nil {
    74  		return nil, fmt.Errorf("failed to get service accounts: %s", err)
    75  	}
    76  
    77  	results := make([]*ServiceAccount, len(typeResponses))
    78  	for sliceIndex := range typeResponses {
    79  		results[sliceIndex] = &ServiceAccount{
    80  			ServiceAccount: typeResponses[sliceIndex],
    81  			org:            org,
    82  		}
    83  	}
    84  
    85  	return results, nil
    86  }
    87  
    88  // GetServiceAccountByName gets a service account by its name
    89  func (org *Org) GetServiceAccountByName(name string) (*ServiceAccount, error) {
    90  	queryParams := url.Values{}
    91  	queryParams.Add("filter", fmt.Sprintf("name==%s", name))
    92  
    93  	serviceAccounts, err := org.GetAllServiceAccounts(queryParams)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("error getting service account by name: %s", err)
    96  	}
    97  
    98  	serviceAccount, err := oneOrError("name", name, serviceAccounts)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	return serviceAccount, nil
   104  }
   105  
   106  // CreateServiceAccount creates a Service Account and sets it in `Created` status
   107  func (vcdClient *VCDClient) CreateServiceAccount(orgName, name, scope, softwareId, softwareVersion, clientUri string) (*ServiceAccount, error) {
   108  	saParams := &types.ApiTokenParams{
   109  		ClientName:      name,
   110  		Scope:           scope,
   111  		SoftwareID:      softwareId,
   112  		SoftwareVersion: softwareVersion,
   113  		ClientURI:       clientUri,
   114  	}
   115  
   116  	newSaParams, err := vcdClient.RegisterToken(orgName, saParams)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("failed to register Service account: %s", err)
   119  	}
   120  
   121  	org, err := vcdClient.GetOrgByName(orgName)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("failed to get Org by name: %s", err)
   124  	}
   125  
   126  	serviceAccountID := "urn:vcloud:serviceAccount:" + newSaParams.ClientID
   127  	serviceAccount, err := org.GetServiceAccountById(serviceAccountID)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("failed to get Service account by ID: %s", err)
   130  	}
   131  
   132  	return serviceAccount, nil
   133  }
   134  
   135  // Update updates the modifiable fields of a Service Account
   136  func (sa *ServiceAccount) Update(saConfig *types.ServiceAccount) (*ServiceAccount, error) {
   137  	client := sa.org.client
   138  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts
   139  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	saConfig.ID = sa.ServiceAccount.ID
   145  	saConfig.Name = sa.ServiceAccount.Name
   146  	saConfig.Status = sa.ServiceAccount.Status
   147  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, saConfig.ID)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	returnServiceAccount := &ServiceAccount{
   153  		ServiceAccount: &types.ServiceAccount{},
   154  		org:            sa.org,
   155  	}
   156  
   157  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, saConfig, returnServiceAccount.ServiceAccount, nil)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("error updating Service Account: %s", err)
   160  	}
   161  
   162  	return returnServiceAccount, nil
   163  }
   164  
   165  // Authorize authorizes a service account and returns a DeviceID and UserCode which will be used while granting
   166  // the request, and sets the Service Account in `Requested` status
   167  func (sa *ServiceAccount) Authorize() error {
   168  	client := sa.org.client
   169  
   170  	uuid := extractUuid(sa.ServiceAccount.ID)
   171  	data := map[string]string{
   172  		"client_id": uuid,
   173  	}
   174  
   175  	userDef := "tenant/" + sa.org.orgName()
   176  	if strings.EqualFold(sa.org.orgName(), "system") {
   177  		userDef = "provider"
   178  	}
   179  
   180  	endpoint := fmt.Sprintf("%s://%s/oauth/%s/device_authorization", client.VCDHREF.Scheme, client.VCDHREF.Host, userDef)
   181  	urlRef, err := url.ParseRequestURI(endpoint)
   182  	if err != nil {
   183  		return fmt.Errorf("error getting request url from %s: %s", urlRef.String(), err)
   184  	}
   185  
   186  	// Not an OpenAPI endpoint so hardcoding the Service Account minimal version
   187  	err = client.OpenApiPostUrlEncoded("37.0", urlRef, nil, data, &sa.authParams, nil)
   188  	if err != nil {
   189  		return fmt.Errorf("error authorizing service account: %s", err)
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // Grant Grants access to the Service Account and sets it in `Granted` status
   196  func (sa *ServiceAccount) Grant() error {
   197  	if sa.authParams == nil {
   198  		return fmt.Errorf("error: userCode is unset, service account needs to be authorized")
   199  	}
   200  
   201  	client := sa.org.client
   202  	// This is the only place where this field is used, so a local struct is created
   203  	type serviceAccountGrant struct {
   204  		UserCode string `json:"userCode"`
   205  	}
   206  
   207  	userCode := &serviceAccountGrant{
   208  		UserCode: sa.authParams.UserCode,
   209  	}
   210  
   211  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccountGrant
   212  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
   218  	if err != nil {
   219  		return fmt.Errorf("error granting service account: %s", err)
   220  	}
   221  
   222  	tenantContext, err := sa.org.getTenantContext()
   223  	if err != nil {
   224  		return fmt.Errorf("error granting service account: %s", err)
   225  	}
   226  
   227  	err = client.OpenApiPostItem(apiVersion, urlRef, nil, userCode, nil, getTenantContextHeader(tenantContext))
   228  	if err != nil {
   229  		return fmt.Errorf("error granting service account: %s", err)
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  // GetInitialApiToken gets the initial API token for the Service Account and sets it in `Active` status
   236  func (sa *ServiceAccount) GetInitialApiToken() (*types.ApiTokenRefresh, error) {
   237  	if sa.authParams == nil {
   238  		return nil, fmt.Errorf("error: service account must be authorized and granted")
   239  	}
   240  	client := sa.org.client
   241  	uuid := extractUuid(sa.ServiceAccount.ID)
   242  	data := map[string]string{
   243  		"grant_type":  "urn:ietf:params:oauth:grant-type:device_code",
   244  		"client_id":   uuid,
   245  		"device_code": sa.authParams.DeviceCode,
   246  	}
   247  	token, err := client.getAccessToken(sa.ServiceAccount.Org.Name, "CreateServiceAccount", data)
   248  	if err != nil {
   249  		return nil, fmt.Errorf("error getting initial api token: %s", err)
   250  	}
   251  	return token, nil
   252  }
   253  
   254  // Refresh updates the Service Account object
   255  func (sa *ServiceAccount) Refresh() error {
   256  	if sa.ServiceAccount == nil || sa.org.client == nil || sa.ServiceAccount.ID == "" {
   257  		return fmt.Errorf("cannot refresh Service Account without ID")
   258  	}
   259  
   260  	updatedServiceAccount, err := sa.org.GetServiceAccountById(sa.ServiceAccount.ID)
   261  	if err != nil {
   262  		return err
   263  	}
   264  	sa.ServiceAccount = updatedServiceAccount.ServiceAccount
   265  
   266  	return nil
   267  }
   268  
   269  // Revoke revokes the service account and its' API token and puts it back in 'Created' stage
   270  func (sa *ServiceAccount) Revoke() error {
   271  	client := sa.org.client
   272  
   273  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts
   274  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, sa.ServiceAccount.ID, "/revoke")
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	err = client.OpenApiPostItem(apiVersion, urlRef, nil, nil, nil, nil)
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  // Delete deletes a Service Account
   293  func (sa *ServiceAccount) Delete() error {
   294  	client := sa.org.client
   295  
   296  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointServiceAccounts
   297  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, sa.ServiceAccount.ID)
   303  	if err != nil {
   304  		return err
   305  	}
   306  
   307  	err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
   308  	if err != nil {
   309  		return err
   310  	}
   311  
   312  	return nil
   313  }
   314  
   315  // SetServiceAccountApiToken gets the refresh token from a provided file, fetches
   316  // the bearer token and updates the provided file with the new refresh token for
   317  // next usage as service account API tokens are one-time use
   318  func (vcdClient *VCDClient) SetServiceAccountApiToken(org, apiTokenFile string) error {
   319  	if vcdClient.Client.APIVCDMaxVersionIs("< 37.0") {
   320  		version, err := vcdClient.Client.GetVcdFullVersion()
   321  		if err == nil {
   322  			return fmt.Errorf("minimum version for Service Account authentication is 10.4 - Version detected: %s", version.Version)
   323  		}
   324  		// If we can't get the VCD version, we return API version info
   325  		return fmt.Errorf("minimum API version for Service Account authentication is 37.0 - Version detected: %s", vcdClient.Client.APIVersion)
   326  	}
   327  
   328  	apiToken, err := vcdClient.SetApiTokenFromFile(org, apiTokenFile)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	err = SaveServiceAccountToFile(apiTokenFile, vcdClient.Client.UserAgent, apiToken)
   334  	if err != nil {
   335  		return fmt.Errorf("failed to save service account token to %s: %s", apiTokenFile, err)
   336  	}
   337  	return nil
   338  }
   339  
   340  // SaveServiceAccountToFile saves the API token of the Service Account to a file
   341  func SaveServiceAccountToFile(filename, useragent string, saToken *types.ApiTokenRefresh) error {
   342  	return saveTokenToFile(filename, "Service Account", useragent, saToken)
   343  }