vmware / go-vcloud-director

Golang SDK for VMware Cloud Director
Other
80 stars 78 forks source link

Auth expire handling for govcd.VCDClient #377

Open lelvisl opened 3 years ago

lelvisl commented 3 years ago

As long as you using *govcd.VCDClient for a long time, its auth expires in accordance with auth timeout settings in vcd.

State now: handle 401 error and reauth, after this repeat request. And you need have this logic near of every govcd call.

Example of solution with http transport:

package vcdreauth

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/vmware/go-vcloud-director/v2/govcd"
)

type Transport struct {
    cli         *govcd.VCDClient
    user        string
    password    string
    org         string
    next        http.RoundTripper
    maxAttempts uint
}

func NewReAuthTransport(cli *govcd.VCDClient, user, password, org string, maxAttempts uint) *Transport {
    if maxAttempts < 1 {
        maxAttempts = DefaultReAuthMaxAttempts
    }
    return &Transport{
        cli:         cli,
        user:        user,
        password:    password,
        org:         org,
        next:        cli.Client.Http.Transport,
        maxAttempts: maxAttempts,
    }
}

const DefaultReAuthMaxAttempts = 1

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
    for count := 0; count < DefaultReAuthMaxAttempts+1; count++ {
        // Perform request
        resp, err := t.next.RoundTrip(req)

        // don't try to retry auth enpoints
        if req.URL.Path == "/api/sessions" || strings.Contains(req.URL.Path, "/cloudapi/1.0.0/sessions/provider") {
            return resp, err
        }

        // if status not 401 - don't try to retry
        if resp.StatusCode != http.StatusUnauthorized {
            return resp, err
        }

        // close the response body when we wont use it anymore
        if resp != nil {
            _ = resp.Body.Close()
        }
        authResp, err := t.cli.GetAuthResponse(t.user, t.password, t.org)
        if err != nil {
            return nil, fmt.Errorf("unable to authenticate: %w", err)
        }

        // re-setup auth headers
        if t.cli.Client.VCDAuthHeader != "" && t.cli.Client.VCDToken != "" {
            // Add the authorization header
            req.Header.Set(t.cli.Client.VCDAuthHeader, t.cli.Client.VCDToken)
        }
        if len(t.cli.Client.VCDToken) > 32 {
            req.Header.Set("X-Vmware-Vcloud-Token-Type", "Bearer")
            req.Header.Set("Authorization", "bearer "+t.cli.Client.VCDToken)
        }

        if authResp != nil {
            _ = authResp.Body.Close()
        }
    }
    return nil, fmt.Errorf("unauthorized - max attempts (%d)", t.maxAttempts)
}

Example of usage:

    vcdclient := govcd.NewVCDClient(*u, Insecure, govcd.WithHttpUserAgent(userAgent))
    vcdclient.Client.Http.Transport = vcdreauth.NewReAuthTransport(vcdclient, User, Password, systemOrg, 1)
    _, err := vcdclient.GetAuthResponse(User, Password, systemOrg)

if you ok with this solution, i can add it as PR.

P.S. I'm already using this code in my project and its working well.

lelvisl commented 3 years ago

@dataclouder is it correct flow for reauth?