Skip to content

Commit

Permalink
v11.0.0
Browse files Browse the repository at this point in the history
Changed a few fields in adal.Token from string to json.Number to handle
differences between AAD and ADFS in how they send data over the wire.
Added auth.NewAuthorizerFromFileWithResource to create an authorizer
from the config file with the specified resource.
Setting a client's PollingDuration to zero will use the provided context
to control a LRO's polling duration.
  • Loading branch information
jhendrixMSFT committed Sep 28, 2018
1 parent 9bc4033 commit 063a7bf
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ _obj
_test
.DS_Store
.idea/
.vscode/

# Architecture specific extensions/prefixes
*.[568vq]
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# CHANGELOG

## v11.0.0

### Breaking Changes

- To handle differences between ADFS and AAD the following fields have had their types changed from `string` to `json.Number`
- ExpiresIn
- ExpiresOn
- NotBefore

### New Features

- Added `auth.NewAuthorizerFromFileWithResource` to create an authorizer from the config file with the specified resource.
- Setting a client's `PollingDuration` to zero will use the provided context to control a LRO's polling duration.

## v10.15.5

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion autorest/adal/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
return sf(r)
}

// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
// SendDecorator takes and possibly decorates, by wrapping, a Sender. Decorators may affect the
// http.Request and pass it along or, first, pass the http.Request along then react to the
// http.Response result.
type SendDecorator func(Sender) Sender
Expand Down
22 changes: 16 additions & 6 deletions autorest/adal/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -97,31 +96,40 @@ type RefresherWithContext interface {
type TokenRefreshCallback func(Token) error

// Token encapsulates the access token used to authorize Azure requests.
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#service-to-service-access-token-response
type Token struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`

ExpiresIn string `json:"expires_in"`
ExpiresOn string `json:"expires_on"`
NotBefore string `json:"not_before"`
ExpiresIn json.Number `json:"expires_in"`
ExpiresOn json.Number `json:"expires_on"`
NotBefore json.Number `json:"not_before"`

Resource string `json:"resource"`
Type string `json:"token_type"`
}

func newToken() Token {
return Token{
ExpiresIn: "0",
ExpiresOn: "0",
NotBefore: "0",
}
}

// IsZero returns true if the token object is zero-initialized.
func (t Token) IsZero() bool {
return t == Token{}
}

// Expires returns the time.Time when the Token expires.
func (t Token) Expires() time.Time {
s, err := strconv.Atoi(t.ExpiresOn)
s, err := t.ExpiresOn.Float64()
if err != nil {
s = -3600
}

expiration := date.NewUnixTimeFromSeconds(float64(s))
expiration := date.NewUnixTimeFromSeconds(s)

return time.Time(expiration).UTC()
}
Expand Down Expand Up @@ -414,6 +422,7 @@ func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, reso
}
spt := &ServicePrincipalToken{
inner: servicePrincipalToken{
Token: newToken(),
OauthConfig: oauthConfig,
Secret: secret,
ClientID: id,
Expand Down Expand Up @@ -653,6 +662,7 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI

spt := &ServicePrincipalToken{
inner: servicePrincipalToken{
Token: newToken(),
OauthConfig: OAuthConfig{
TokenEndpoint: *msiEndpointURL,
},
Expand Down
30 changes: 12 additions & 18 deletions autorest/adal/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,8 @@ func TestServicePrincipalTokenRefreshUnmarshals(t *testing.T) {
t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err)
} else if spt.inner.Token.AccessToken != "accessToken" ||
spt.inner.Token.ExpiresIn != "3600" ||
spt.inner.Token.ExpiresOn != expiresOn ||
spt.inner.Token.NotBefore != expiresOn ||
spt.inner.Token.ExpiresOn != json.Number(expiresOn) ||
spt.inner.Token.NotBefore != json.Number(expiresOn) ||
spt.inner.Token.Resource != "resource" ||
spt.inner.Token.Type != "Bearer" {
t.Fatalf("adal: ServicePrincipalToken#Refresh failed correctly unmarshal the JSON -- expected %v, received %v",
Expand Down Expand Up @@ -684,13 +684,13 @@ func TestNewServicePrincipalTokenFromManualTokenSecret(t *testing.T) {
RedirectURI: "redirect",
}

spt, err := NewServicePrincipalTokenFromManualTokenSecret(TestOAuthConfig, "id", "resource", *token, secret, nil)
spt, err := NewServicePrincipalTokenFromManualTokenSecret(TestOAuthConfig, "id", "resource", token, secret, nil)
if err != nil {
t.Fatalf("Failed creating new SPT: %s", err)
}

if !reflect.DeepEqual(*token, spt.inner.Token) {
t.Fatalf("Tokens do not match: %s, %s", *token, spt.inner.Token)
if !reflect.DeepEqual(token, spt.inner.Token) {
t.Fatalf("Tokens do not match: %s, %s", token, spt.inner.Token)
}

if !reflect.DeepEqual(secret, spt.inner.Secret) {
Expand Down Expand Up @@ -822,7 +822,7 @@ func TestMarshalInnerToken(t *testing.T) {
t.Fatalf("tokens don't match: %s, %s", tokenJSON, testTokenJSON)
}

var t1 *Token
var t1 Token
err = json.Unmarshal(tokenJSON, &t1)
if err != nil {
t.Fatalf("failed to unmarshal token: %+v", err)
Expand All @@ -833,14 +833,6 @@ func TestMarshalInnerToken(t *testing.T) {
}
}

func newToken() *Token {
return &Token{
AccessToken: "ASECRETVALUE",
Resource: "https://azure.microsoft.com/",
Type: "Bearer",
}
}

func newTokenJSON(expiresOn string, resource string) string {
return fmt.Sprintf(`{
"access_token" : "accessToken",
Expand All @@ -855,11 +847,13 @@ func newTokenJSON(expiresOn string, resource string) string {
}

func newTokenExpiresIn(expireIn time.Duration) *Token {
return setTokenToExpireIn(newToken(), expireIn)
t := newToken()
return setTokenToExpireIn(&t, expireIn)
}

func newTokenExpiresAt(expireAt time.Time) *Token {
return setTokenToExpireAt(newToken(), expireAt)
t := newToken()
return setTokenToExpireAt(&t, expireAt)
}

func expireToken(t *Token) *Token {
Expand All @@ -868,7 +862,7 @@ func expireToken(t *Token) *Token {

func setTokenToExpireAt(t *Token, expireAt time.Time) *Token {
t.ExpiresIn = "3600"
t.ExpiresOn = strconv.Itoa(int(expireAt.Sub(date.UnixEpoch()).Seconds()))
t.ExpiresOn = json.Number(strconv.FormatInt(int64(expireAt.Sub(date.UnixEpoch())/time.Second), 10))
t.NotBefore = t.ExpiresOn
return t
}
Expand All @@ -885,7 +879,7 @@ func newServicePrincipalToken(callbacks ...TokenRefreshCallback) *ServicePrincip
func newServicePrincipalTokenManual() *ServicePrincipalToken {
token := newToken()
token.RefreshToken = "refreshtoken"
spt, _ := NewServicePrincipalTokenFromManualToken(TestOAuthConfig, "id", "resource", *token)
spt, _ := NewServicePrincipalTokenFromManualToken(TestOAuthConfig, "id", "resource", token)
return spt
}

Expand Down
8 changes: 6 additions & 2 deletions autorest/azure/async.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,12 @@ func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) e
// polling duration has been exceeded. It will retry failed polling attempts based on
// the retry value defined in the client up to the maximum retry attempts.
func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) error {
ctx, cancel := context.WithTimeout(ctx, client.PollingDuration)
defer cancel()
if d := client.PollingDuration; d != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, d)
defer cancel()
}

done, err := f.Done(client)
for attempts := 0; !done; done, err = f.Done(client) {
if attempts >= client.RetryAttempts {
Expand Down
42 changes: 30 additions & 12 deletions autorest/azure/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,44 +135,62 @@ func (settings settings) getAuthorizer() (autorest.Authorizer, error) {

// NewAuthorizerFromFile creates an Authorizer configured from a configuration file.
func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) {
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
if fileLocation == "" {
return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
file, err := getAuthFile()
if err != nil {
return nil, err
}

contents, err := ioutil.ReadFile(fileLocation)
resource, err := getResourceForToken(*file, baseURI)
if err != nil {
return nil, err
}
return NewAuthorizerFromFileWithResource(resource)
}

// Auth file might be encoded
decoded, err := decode(contents)
// NewAuthorizerFromFileWithResource creates an Authorizer configured from a configuration file.
func NewAuthorizerFromFileWithResource(resource string) (autorest.Authorizer, error) {
file, err := getAuthFile()
if err != nil {
return nil, err
}

file := file{}
err = json.Unmarshal(decoded, &file)
config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
if err != nil {
return nil, err
}

resource, err := getResourceForToken(file, baseURI)
spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
if err != nil {
return nil, err
}

config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
return autorest.NewBearerAuthorizer(spToken), nil
}

func getAuthFile() (*file, error) {
fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
if fileLocation == "" {
return nil, errors.New("environment variable AZURE_AUTH_LOCATION is not set")
}

contents, err := ioutil.ReadFile(fileLocation)
if err != nil {
return nil, err
}

spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
// Auth file might be encoded
decoded, err := decode(contents)
if err != nil {
return nil, err
}

return autorest.NewBearerAuthorizer(spToken), nil
authFile := file{}
err = json.Unmarshal(decoded, &authFile)
if err != nil {
return nil, err
}

return &authFile, nil
}

// File represents the authentication file
Expand Down
23 changes: 22 additions & 1 deletion autorest/azure/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,35 @@ func TestNewAuthorizerFromFile(t *testing.T) {
}
}

func TestNewAuthorizerFromFileWithResource(t *testing.T) {
os.Setenv("AZURE_AUTH_LOCATION", filepath.Join(getCredsPath(), "credsutf16le.json"))
authorizer, err := NewAuthorizerFromFileWithResource("https://my.vault.azure.net")
if err != nil || authorizer == nil {
t.Logf("NewAuthorizerFromFileWithResource failed, got error %v", err)
t.Fail()
}
}

func TestNewAuthorizerFromEnvironment(t *testing.T) {
os.Setenv("AZURE_TENANT_ID", expectedFile.TenantID)
os.Setenv("AZURE_CLIENT_ID", expectedFile.ClientID)
os.Setenv("AZURE_CLIENT_SECRET", expectedFile.ClientSecret)
authorizer, err := NewAuthorizerFromEnvironment()

if err != nil || authorizer == nil {
t.Logf("NewAuthorizerFromFile failed, got error %v", err)
t.Logf("NewAuthorizerFromEnvironment failed, got error %v", err)
t.Fail()
}
}

func TestNewAuthorizerFromEnvironmentWithResource(t *testing.T) {
os.Setenv("AZURE_TENANT_ID", expectedFile.TenantID)
os.Setenv("AZURE_CLIENT_ID", expectedFile.ClientID)
os.Setenv("AZURE_CLIENT_SECRET", expectedFile.ClientSecret)
authorizer, err := NewAuthorizerFromEnvironmentWithResource("https://my.vault.azure.net")

if err != nil || authorizer == nil {
t.Logf("NewAuthorizerFromEnvironmentWithResource failed, got error %v", err)
t.Fail()
}
}
Expand Down
2 changes: 1 addition & 1 deletion autorest/azure/cli/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (t Token) ToADALToken() (converted adal.Token, err error) {
AccessToken: t.AccessToken,
Type: t.TokenType,
ExpiresIn: "3600",
ExpiresOn: strconv.Itoa(int(difference.Seconds())),
ExpiresOn: json.Number(strconv.Itoa(int(difference.Seconds()))),
RefreshToken: t.RefreshToken,
Resource: t.Resource,
}
Expand Down
6 changes: 3 additions & 3 deletions autorest/azure/rp.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError
}

// poll for registered provisioning state
now := time.Now()
for err == nil && time.Since(now) < client.PollingDuration {
registrationStartTime := time.Now()
for err == nil && (client.PollingDuration == 0 || (client.PollingDuration != 0 && time.Since(registrationStartTime) < client.PollingDuration)) {
// taken from the resources SDK
// https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45
preparer := autorest.CreatePreparer(
Expand Down Expand Up @@ -183,7 +183,7 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError
return originalReq.Context().Err()
}
}
if !(time.Since(now) < client.PollingDuration) {
if client.PollingDuration != 0 && !(time.Since(registrationStartTime) < client.PollingDuration) {
return errors.New("polling for resource provider registration has exceeded the polling duration")
}
return err
Expand Down
1 change: 1 addition & 0 deletions autorest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ type Client struct {
PollingDelay time.Duration

// PollingDuration sets the maximum polling time after which an error is returned.
// Setting this to zero will use the provided context to control the duration.
PollingDuration time.Duration

// RetryAttempts sets the default number of retry attempts for client.
Expand Down
2 changes: 1 addition & 1 deletion version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

// Number contains the semantic version of this SDK.
const Number = "v10.15.5"
const Number = "v11.0.0"

var (
userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s",
Expand Down

0 comments on commit 063a7bf

Please sign in to comment.