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 }