Skip to content

Commit

Permalink
v11.0.0 (#326)
Browse files Browse the repository at this point in the history
* v11.0.0

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.

* Update dependencies
  • Loading branch information
jhendrixMSFT authored Sep 29, 2018
1 parent 9bc4033 commit 87f168d
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 55 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
8 changes: 4 additions & 4 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,13 @@
version = "3.1.0"

[[constraint]]
branch = "master"
name = "github.com/dimchansky/utfbom"
version = "1.0.0"

[[constraint]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
version = "1.0.0"

[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.0"

[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
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
Loading

0 comments on commit 87f168d

Please sign in to comment.