diff --git a/azuredevops/v7/auth.go b/azuredevops/v7/auth.go new file mode 100644 index 0000000..966a39b --- /dev/null +++ b/azuredevops/v7/auth.go @@ -0,0 +1,13 @@ +package azuredevops + +import ( + "context" +) + +type Auth struct { + AuthString string +} + +type AuthProvider interface { + GetAuth(ctx context.Context) (string, error) +} diff --git a/azuredevops/v7/auth_aad.go b/azuredevops/v7/auth_aad.go new file mode 100644 index 0000000..28f66e9 --- /dev/null +++ b/azuredevops/v7/auth_aad.go @@ -0,0 +1,30 @@ +package azuredevops + +import ( + "context" + "fmt" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" +) + +type AADCred interface { + GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) +} + +type AuthProviderAAD struct { + cred AADCred + opts policy.TokenRequestOptions +} + +func NewAuthProviderAAD(cred AADCred, opts policy.TokenRequestOptions) AuthProvider { + return AuthProviderAAD{cred, opts} +} + +func (p AuthProviderAAD) GetAuth(ctx context.Context) (string, error) { + token, err := p.cred.GetToken(ctx, p.opts) + if err != nil { + return "", fmt.Errorf("failed to get AAD token: %v", err) + } + return "Bearer " + token.Token, nil +} diff --git a/azuredevops/v7/auth_pat.go b/azuredevops/v7/auth_pat.go new file mode 100644 index 0000000..a0149d1 --- /dev/null +++ b/azuredevops/v7/auth_pat.go @@ -0,0 +1,19 @@ +package azuredevops + +import ( + "context" + "encoding/base64" +) + +type AuthProviderPAT struct { + pat string +} + +func NewAuthProviderPAT(pat string) AuthProvider { + return AuthProviderPAT{pat} +} + +func (p AuthProviderPAT) GetAuth(_ context.Context) (string, error) { + auth := "_:" + p.pat + return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)), nil +} diff --git a/azuredevops/v7/client.go b/azuredevops/v7/client.go index e0d0d4c..766d032 100644 --- a/azuredevops/v7/client.go +++ b/azuredevops/v7/client.go @@ -8,6 +8,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -70,7 +71,7 @@ func NewClientWithOptions(connection *Connection, baseUrl string, options ...Cli client := &Client{ baseUrl: baseUrl, client: httpClient, - authorization: connection.AuthorizationString, + authProvider: connection.AuthProvider, suppressFedAuthRedirect: connection.SuppressFedAuthRedirect, forceMsaPassThrough: connection.ForceMsaPassThrough, userAgent: connection.UserAgent, @@ -84,7 +85,7 @@ func NewClientWithOptions(connection *Connection, baseUrl string, options ...Cli type Client struct { baseUrl string client *http.Client - authorization string + authProvider AuthProvider suppressFedAuthRedirect bool forceMsaPassThrough bool userAgent string @@ -169,9 +170,14 @@ func (client *Client) CreateRequestMessage(ctx context.Context, req = req.WithContext(ctx) } - if client.authorization != "" { - req.Header.Add(headerKeyAuthorization, client.authorization) + if client.authProvider != nil { + auth, err := client.authProvider.GetAuth(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get auth from auth cache: %v", err) + } + req.Header.Add(headerKeyAuthorization, auth) } + accept := acceptMediaType if apiVersion != "" { accept += ";api-version=" + apiVersion diff --git a/azuredevops/v7/connection.go b/azuredevops/v7/connection.go index eb76f53..e954b5d 100644 --- a/azuredevops/v7/connection.go +++ b/azuredevops/v7/connection.go @@ -16,10 +16,10 @@ import ( // Creates a new Azure DevOps connection instance using a personal access token. func NewPatConnection(organizationUrl string, personalAccessToken string) *Connection { - authorizationString := CreateBasicAuthHeaderValue("", personalAccessToken) organizationUrl = normalizeUrl(organizationUrl) + authProvider := NewAuthProviderPAT(personalAccessToken) return &Connection{ - AuthorizationString: authorizationString, + AuthProvider: authProvider, BaseUrl: organizationUrl, SuppressFedAuthRedirect: true, } @@ -34,7 +34,7 @@ func NewAnonymousConnection(organizationUrl string) *Connection { } type Connection struct { - AuthorizationString string + AuthProvider AuthProvider BaseUrl string UserAgent string SuppressFedAuthRedirect bool diff --git a/azuredevops/v7/go.mod b/azuredevops/v7/go.mod index 0c3ebc6..5c909f4 100644 --- a/azuredevops/v7/go.mod +++ b/azuredevops/v7/go.mod @@ -1,5 +1,16 @@ module github.com/microsoft/azure-devops-go-api/azuredevops/v7 -go 1.12 +go 1.23.0 -require github.com/google/uuid v1.1.1 +toolchain go1.24.1 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 + github.com/google/uuid v1.6.0 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/text v0.23.0 // indirect +) diff --git a/azuredevops/v7/go.sum b/azuredevops/v7/go.sum index b864886..39e35ef 100644 --- a/azuredevops/v7/go.sum +++ b/azuredevops/v7/go.sum @@ -1,2 +1,18 @@ -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=