Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v11.0.0 #326

Merged
merged 2 commits into from
Sep 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a good moment to add some documentation about the expected contents of these values as well.

For example, I believe this is a whole number of seconds. We should say so :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a link to the docs for this type.

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