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

     1  /*
     2   * Copyright 2023 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  
     5  package govcd
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/fs"
    11  	"net/url"
    12  	"os"
    13  	"path"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    18  )
    19  
    20  // TODO Have distinct names for API and Refresh tokens
    21  // Token is a struct that handles two methods: Delete() and GetInitialRefreshToken()
    22  type Token struct {
    23  	Token  *types.Token
    24  	client *Client
    25  }
    26  
    27  // CreateToken is used for creating API tokens and works in two steps:
    28  // 1. Register the token through the `register` endpoint
    29  // 2. Fetch it using GetTokenById(tokenID)
    30  // The user then can use *Token.GetInitialRefreshToken to get the API token
    31  func (vcdClient *VCDClient) CreateToken(org, tokenName string) (*Token, error) {
    32  	apiTokenParams := &types.ApiTokenParams{
    33  		ClientName: tokenName,
    34  	}
    35  
    36  	newTokenParams, err := vcdClient.RegisterToken(org, apiTokenParams)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("failed to register API token: %s", err)
    39  	}
    40  
    41  	tokenUrn, err := BuildUrnWithUuid("urn:vcloud:token:", newTokenParams.ClientID)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("failed to build URN: %s", err)
    44  	}
    45  
    46  	token, err := vcdClient.GetTokenById(tokenUrn)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("failed to get token: %s", err)
    49  	}
    50  
    51  	return token, nil
    52  }
    53  
    54  // GetTokenById retrieves a Token by ID
    55  func (vcdClient *VCDClient) GetTokenById(tokenId string) (*Token, error) {
    56  	client := vcdClient.Client
    57  
    58  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTokens
    59  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, tokenId)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	apiToken := &Token{
    70  		Token:  &types.Token{},
    71  		client: &client,
    72  	}
    73  
    74  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, apiToken.Token, nil)
    75  	if err != nil {
    76  		return nil, fmt.Errorf("failed to get token: %s", err)
    77  	}
    78  
    79  	return apiToken, nil
    80  }
    81  
    82  // GetAllTokens gets all tokens with the specified query parameters
    83  func (vcdClient *VCDClient) GetAllTokens(queryParameters url.Values) ([]*Token, error) {
    84  	client := vcdClient.Client
    85  
    86  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTokens
    87  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	typeResponses := []*types.Token{{}}
    98  	err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponses, nil)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("failed to get tokens: %s", err)
   101  	}
   102  
   103  	results := make([]*Token, len(typeResponses))
   104  	for sliceIndex := range typeResponses {
   105  		results[sliceIndex] = &Token{
   106  			Token:  typeResponses[sliceIndex],
   107  			client: &client,
   108  		}
   109  	}
   110  
   111  	return results, nil
   112  }
   113  
   114  // GetTokenByNameAndUsername retrieves a Token by name and username
   115  func (vcdClient *VCDClient) GetTokenByNameAndUsername(tokenName, userName string) (*Token, error) {
   116  	queryParameters := url.Values{}
   117  	queryParameters.Add("filter", fmt.Sprintf("(name==%s;owner.name==%s;(type==PROXY,type==REFRESH))", tokenName, userName))
   118  
   119  	tokens, err := vcdClient.GetAllTokens(queryParameters)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to get token by name and owner: %s", err)
   122  	}
   123  
   124  	token, err := oneOrError("name", tokenName, tokens)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return token, nil
   130  }
   131  
   132  // RegisterToken registers an API token with the given name. The access token can still be fetched for the API
   133  // token using token.GetInitialApiToken()
   134  func (vcdClient *VCDClient) RegisterToken(org string, tokenParams *types.ApiTokenParams) (*types.ApiTokenParams, error) {
   135  	client := vcdClient.Client
   136  
   137  	if client.APIVCDMaxVersionIs("< 36.1") {
   138  		version, err := client.GetVcdFullVersion()
   139  		if err == nil {
   140  			return nil, fmt.Errorf("minimum version for Token registration is 10.3.1 - Version detected: %s", version.Version)
   141  		}
   142  		// If we can't get the VCD version, we return API version info
   143  		return nil, fmt.Errorf("minimum API version for Token registration is 36.1 - Version detected: %s", client.APIVersion)
   144  	}
   145  
   146  	// If the client is a user of an org, the endpoint is oauth/tenant/orgName/register
   147  	// if the client is a user of sysorg, the endpoint is oauth/provider/register
   148  	userDef := "tenant/" + org
   149  	if strings.EqualFold(org, "system") {
   150  		userDef = "provider"
   151  	}
   152  
   153  	// Create the URL for the register endpoint
   154  	urlRef, err := url.ParseRequestURI(fmt.Sprintf("%s://%s/oauth/%s/%s", client.VCDHREF.Scheme, client.VCDHREF.Host, userDef, "register"))
   155  	if err != nil {
   156  		return nil, fmt.Errorf("error getting request URL from %s : %s", urlRef.String(), err)
   157  	}
   158  
   159  	newTokenParams := &types.ApiTokenParams{}
   160  
   161  	// oauth/{tenantcontext}/register isn't an OpenAPI endpoint, so it doesn't have a defined
   162  	// API version
   163  	err = client.OpenApiPostItemSync("", urlRef, nil, tokenParams, newTokenParams)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("error registering token: %s", err)
   166  	}
   167  
   168  	return newTokenParams, nil
   169  }
   170  
   171  // getAccessToken gets the access token structure containing the bearer token
   172  func (client *Client) getAccessToken(org, funcName string, payloadMap map[string]string) (*types.ApiTokenRefresh, error) {
   173  	userDef := "tenant/" + org
   174  	if strings.EqualFold(org, "system") {
   175  		userDef = "provider"
   176  	}
   177  
   178  	endpoint := fmt.Sprintf("%s://%s/oauth/%s/token", client.VCDHREF.Scheme, client.VCDHREF.Host, userDef)
   179  	urlRef, err := url.ParseRequestURI(endpoint)
   180  	if err != nil {
   181  		return nil, fmt.Errorf("error getting request url from %s: %s", urlRef.String(), err)
   182  	}
   183  
   184  	newToken := &types.ApiTokenRefresh{}
   185  
   186  	// Not an OpenAPI endpoint so hardcoding the API token minimal version
   187  	err = client.OpenApiPostUrlEncoded("36.1", urlRef, nil, payloadMap, &newToken, nil)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("error authorizing service account: %s", err)
   190  	}
   191  
   192  	return newToken, nil
   193  }
   194  
   195  // GetInitialApiToken gets the initial API token, usable only once per token.
   196  func (token *Token) GetInitialApiToken() (*types.ApiTokenRefresh, error) {
   197  	client := token.client
   198  	uuid := extractUuid(token.Token.ID)
   199  	data := map[string]string{
   200  		"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
   201  		"assertion":  client.VCDToken,
   202  		"client_id":  uuid,
   203  	}
   204  
   205  	refreshToken, err := client.getAccessToken(token.Token.Org.Name, "CreateApiToken", data)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("error getting token: %s", err)
   208  	}
   209  
   210  	return refreshToken, nil
   211  }
   212  
   213  // DeleteTokenByID deletes an existing token by its' URN ID
   214  func (token *Token) Delete() error {
   215  	client := token.client
   216  
   217  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTokens
   218  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, token.Token.ID)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  // SetApiToken behaves similarly to SetToken, with the difference that it will
   237  // return full information about the bearer token, so that the caller can make decisions about token expiration
   238  func (vcdClient *VCDClient) SetApiToken(org, apiToken string) (*types.ApiTokenRefresh, error) {
   239  	tokenRefresh, err := vcdClient.GetBearerTokenFromApiToken(org, apiToken)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	err = vcdClient.SetToken(org, BearerTokenHeader, tokenRefresh.AccessToken)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	return tokenRefresh, nil
   248  }
   249  
   250  // GetBearerTokenFromApiToken uses an API token to retrieve a bearer token
   251  // using the refresh token operation.
   252  func (vcdClient *VCDClient) GetBearerTokenFromApiToken(org, token string) (*types.ApiTokenRefresh, error) {
   253  	data := map[string]string{
   254  		"grant_type":    "refresh_token",
   255  		"refresh_token": token,
   256  	}
   257  	tokenDef, err := vcdClient.Client.getAccessToken(org, "GetBearerTokenFromApiToken", data)
   258  	if err != nil {
   259  		return nil, fmt.Errorf("error getting bearer token: %s", err)
   260  	}
   261  
   262  	return tokenDef, nil
   263  }
   264  
   265  // SetApiTokenFile reads the API token file, sets the client's bearer
   266  // token and fetches a new API token for next authentication request
   267  // using SetApiToken
   268  func (vcdClient *VCDClient) SetApiTokenFromFile(org, apiTokenFile string) (*types.ApiTokenRefresh, error) {
   269  	apiToken, err := GetTokenFromFile(apiTokenFile)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	return vcdClient.SetApiToken(org, apiToken.RefreshToken)
   275  }
   276  
   277  func SaveApiTokenToFile(filename, userAgent string, apiToken *types.ApiTokenRefresh) error {
   278  	return saveTokenToFile(filename, "API Token", userAgent, apiToken)
   279  }
   280  
   281  // GetTokenFromFile reads an API token from a given file
   282  func GetTokenFromFile(tokenFilename string) (*types.ApiTokenRefresh, error) {
   283  	apiToken := &types.ApiTokenRefresh{}
   284  	// Read file contents and unmarshal them to apiToken
   285  	err := readFileAndUnmarshalJSON(tokenFilename, apiToken)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  
   290  	return apiToken, nil
   291  }
   292  
   293  func saveTokenToFile(filename, tokenType, userAgent string, token *types.ApiTokenRefresh) error {
   294  	token = &types.ApiTokenRefresh{
   295  		RefreshToken: token.RefreshToken,
   296  		TokenType:    tokenType,
   297  		UpdatedBy:    userAgent,
   298  		UpdatedOn:    time.Now().Format(time.RFC3339),
   299  	}
   300  	err := marshalJSONAndWriteToFile(filename, token, 0600)
   301  	if err != nil {
   302  		return err
   303  	}
   304  	return nil
   305  }
   306  
   307  // readFileAndUnmarshalJSON reads a file and unmarshals it to the given variable
   308  func readFileAndUnmarshalJSON(filename string, object any) error {
   309  	data, err := os.ReadFile(path.Clean(filename))
   310  	if err != nil {
   311  		return fmt.Errorf("failed to read from file: %s", err)
   312  	}
   313  
   314  	err = json.Unmarshal(data, object)
   315  	if err != nil {
   316  		return fmt.Errorf("failed to unmarshal file contents to the object: %s", err)
   317  	}
   318  
   319  	return nil
   320  }
   321  
   322  // marshalJSONAndWriteToFile marshalls the given object into JSON and writes
   323  // to a file with the given permissions in octal format (e.g 0600)
   324  func marshalJSONAndWriteToFile(filename string, object any, permissions int) error {
   325  	data, err := json.MarshalIndent(object, " ", " ")
   326  	if err != nil {
   327  		return fmt.Errorf("error marshalling object to JSON: %s", err)
   328  	}
   329  
   330  	err = os.WriteFile(filename, data, fs.FileMode(permissions))
   331  	if err != nil {
   332  		return fmt.Errorf("error writing to the file: %s", err)
   333  	}
   334  
   335  	return nil
   336  }