Update github.com/xenolf/lego to 0.4.1

This commit is contained in:
Tait Clarridge 2017-10-31 05:42:03 -04:00 committed by Traefiker
parent 5042c5bf40
commit e8d63b2a3b
105 changed files with 4299 additions and 2075 deletions

View file

@ -0,0 +1,51 @@
package adal
import (
"fmt"
"net/url"
)
const (
activeDirectoryAPIVersion = "1.0"
)
// OAuthConfig represents the endpoints needed
// in OAuth operations
type OAuthConfig struct {
AuthorityEndpoint url.URL
AuthorizeEndpoint url.URL
TokenEndpoint url.URL
DeviceCodeEndpoint url.URL
}
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s"
u, err := url.Parse(activeDirectoryEndpoint)
if err != nil {
return nil, err
}
authorityURL, err := u.Parse(tenantID)
if err != nil {
return nil, err
}
authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
return &OAuthConfig{
AuthorityEndpoint: *authorityURL,
AuthorizeEndpoint: *authorizeURL,
TokenEndpoint: *tokenURL,
DeviceCodeEndpoint: *deviceCodeURL,
}, nil
}

View file

@ -1,4 +1,4 @@
package azure
package adal
/*
This file is largely based on rjw57/oauth2device's code, with the follow differences:
@ -10,16 +10,17 @@ package azure
*/
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/Azure/go-autorest/autorest"
)
const (
logPrefix = "autorest/azure/devicetoken:"
logPrefix = "autorest/adal/devicetoken:"
)
var (
@ -38,10 +39,17 @@ var (
// ErrDeviceSlowDown represents the service telling us we're polling too often during device flow
ErrDeviceSlowDown = fmt.Errorf("%s Error while retrieving OAuth token: Slow Down", logPrefix)
// ErrDeviceCodeEmpty represents an empty device code from the device endpoint while using device flow
ErrDeviceCodeEmpty = fmt.Errorf("%s Error while retrieving device code: Device Code Empty", logPrefix)
// ErrOAuthTokenEmpty represents an empty OAuth token from the token endpoint when using device flow
ErrOAuthTokenEmpty = fmt.Errorf("%s Error while retrieving OAuth token: Token Empty", logPrefix)
errCodeSendingFails = "Error occurred while sending request for Device Authorization Code"
errCodeHandlingFails = "Error occurred while handling response from the Device Endpoint"
errTokenSendingFails = "Error occurred while sending request with device code for a token"
errTokenHandlingFails = "Error occurred while handling response from the Token Endpoint (during device flow)"
errStatusNotOK = "Error HTTP status != 200"
)
// DeviceCode is the object returned by the device auth endpoint
@ -79,31 +87,45 @@ type deviceToken struct {
// InitiateDeviceAuth initiates a device auth flow. It returns a DeviceCode
// that can be used with CheckForUserCompletion or WaitForUserCompletion.
func InitiateDeviceAuth(client *autorest.Client, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
req, _ := autorest.Prepare(
&http.Request{},
autorest.AsPost(),
autorest.AsFormURLEncoded(),
autorest.WithBaseURL(oauthConfig.DeviceCodeEndpoint.String()),
autorest.WithFormData(url.Values{
"client_id": []string{clientID},
"resource": []string{resource},
}),
)
func InitiateDeviceAuth(sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
v := url.Values{
"client_id": []string{clientID},
"resource": []string{resource},
}
resp, err := autorest.SendWithSender(client, req)
s := v.Encode()
body := ioutil.NopCloser(strings.NewReader(s))
req, err := http.NewRequest(http.MethodPost, oauthConfig.DeviceCodeEndpoint.String(), body)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err)
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
}
req.ContentLength = int64(len(s))
req.Header.Set(contentType, mimeTypeFormPost)
resp, err := sender.Do(req)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error())
}
defer resp.Body.Close()
rb, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, errStatusNotOK)
}
if len(strings.Trim(string(rb), " ")) == 0 {
return nil, ErrDeviceCodeEmpty
}
var code DeviceCode
err = autorest.Respond(
resp,
autorest.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&code),
autorest.ByClosing())
err = json.Unmarshal(rb, &code)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err)
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
}
code.ClientID = clientID
@ -115,33 +137,46 @@ func InitiateDeviceAuth(client *autorest.Client, oauthConfig OAuthConfig, client
// CheckForUserCompletion takes a DeviceCode and checks with the Azure AD OAuth endpoint
// to see if the device flow has: been completed, timed out, or otherwise failed
func CheckForUserCompletion(client *autorest.Client, code *DeviceCode) (*Token, error) {
req, _ := autorest.Prepare(
&http.Request{},
autorest.AsPost(),
autorest.AsFormURLEncoded(),
autorest.WithBaseURL(code.OAuthConfig.TokenEndpoint.String()),
autorest.WithFormData(url.Values{
"client_id": []string{code.ClientID},
"code": []string{*code.DeviceCode},
"grant_type": []string{OAuthGrantTypeDeviceCode},
"resource": []string{code.Resource},
}),
)
func CheckForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
v := url.Values{
"client_id": []string{code.ClientID},
"code": []string{*code.DeviceCode},
"grant_type": []string{OAuthGrantTypeDeviceCode},
"resource": []string{code.Resource},
}
resp, err := autorest.SendWithSender(client, req)
s := v.Encode()
body := ioutil.NopCloser(strings.NewReader(s))
req, err := http.NewRequest(http.MethodPost, code.OAuthConfig.TokenEndpoint.String(), body)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err)
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
}
req.ContentLength = int64(len(s))
req.Header.Set(contentType, mimeTypeFormPost)
resp, err := sender.Do(req)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error())
}
defer resp.Body.Close()
rb, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
}
if resp.StatusCode != http.StatusOK && len(strings.Trim(string(rb), " ")) == 0 {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, errStatusNotOK)
}
if len(strings.Trim(string(rb), " ")) == 0 {
return nil, ErrOAuthTokenEmpty
}
var token deviceToken
err = autorest.Respond(
resp,
autorest.WithErrorUnlessStatusCode(http.StatusOK, http.StatusBadRequest),
autorest.ByUnmarshallingJSON(&token),
autorest.ByClosing())
err = json.Unmarshal(rb, &token)
if err != nil {
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err)
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
}
if token.Error == nil {
@ -164,12 +199,12 @@ func CheckForUserCompletion(client *autorest.Client, code *DeviceCode) (*Token,
// WaitForUserCompletion calls CheckForUserCompletion repeatedly until a token is granted or an error state occurs.
// This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'.
func WaitForUserCompletion(client *autorest.Client, code *DeviceCode) (*Token, error) {
func WaitForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) {
intervalDuration := time.Duration(*code.Interval) * time.Second
waitDuration := intervalDuration
for {
token, err := CheckForUserCompletion(client, code)
token, err := CheckForUserCompletion(sender, code)
if err == nil {
return token, nil

View file

@ -0,0 +1,6 @@
// +build !windows
package adal
// msiPath is the path to the MSI Extension settings file (to discover the endpoint)
var msiPath = "/var/lib/waagent/ManagedIdentity-Settings"

View file

@ -0,0 +1,11 @@
// +build windows
package adal
import (
"os"
"strings"
)
// msiPath is the path to the MSI Extension settings file (to discover the endpoint)
var msiPath = strings.Join([]string{os.Getenv("SystemDrive"), "WindowsAzure/Config/ManagedIdentity-Settings"}, "/")

View file

@ -1,4 +1,4 @@
package azure
package adal
import (
"encoding/json"

View file

@ -0,0 +1,46 @@
package adal
import (
"net/http"
)
const (
contentType = "Content-Type"
mimeTypeFormPost = "application/x-www-form-urlencoded"
)
// Sender is the interface that wraps the Do method to send HTTP requests.
//
// The standard http.Client conforms to this interface.
type Sender interface {
Do(*http.Request) (*http.Response, error)
}
// SenderFunc is a method that implements the Sender interface.
type SenderFunc func(*http.Request) (*http.Response, error)
// Do implements the Sender interface on SenderFunc.
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
// 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
// CreateSender creates, decorates, and returns, as a Sender, the default http.Client.
func CreateSender(decorators ...SendDecorator) Sender {
return DecorateSender(&http.Client{}, decorators...)
}
// DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to
// the Sender. Decorators are applied in the order received, but their affect upon the request
// depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a
// post-decorator (pass the http.Request along and react to the results in http.Response).
func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
for _, decorate := range decorators {
s = decorate(s)
}
return s
}

View file

@ -1,4 +1,4 @@
package azure
package adal
import (
"crypto/rand"
@ -6,19 +6,21 @@ import (
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/date"
"github.com/dgrijalva/jwt-go"
)
const (
defaultRefresh = 5 * time.Minute
tokenBaseDate = "1970-01-01T00:00:00Z"
// OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow
OAuthGrantTypeDeviceCode = "device_code"
@ -28,12 +30,21 @@ const (
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
OAuthGrantTypeRefreshToken = "refresh_token"
// metadataHeader is the header required by MSI extension
metadataHeader = "Metadata"
)
var expirationBase time.Time
// OAuthTokenProvider is an interface which should be implemented by an access token retriever
type OAuthTokenProvider interface {
OAuthToken() string
}
func init() {
expirationBase, _ = time.Parse(time.RFC3339, tokenBaseDate)
// Refresher is an interface for token refresh functionality
type Refresher interface {
Refresh() error
RefreshExchange(resource string) error
EnsureFresh() error
}
// TokenRefreshCallback is the type representing callbacks that will be called after
@ -59,7 +70,10 @@ func (t Token) Expires() time.Time {
if err != nil {
s = -3600
}
return expirationBase.Add(time.Duration(s) * time.Second).UTC()
expiration := date.NewUnixTimeFromSeconds(float64(s))
return time.Time(expiration).UTC()
}
// IsExpired returns true if the Token is expired, false otherwise.
@ -73,14 +87,9 @@ func (t Token) WillExpireIn(d time.Duration) bool {
return !t.Expires().After(time.Now().Add(d))
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "Bearer " followed by the AccessToken of the Token.
func (t *Token) WithAuthorization() autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
return (autorest.WithBearerAuthorization(t.AccessToken)(p)).Prepare(r)
})
}
//OAuthToken return the current access token
func (t *Token) OAuthToken() string {
return t.AccessToken
}
// ServicePrincipalNoSecret represents a secret type that contains no secret
@ -118,6 +127,15 @@ type ServicePrincipalCertificateSecret struct {
PrivateKey *rsa.PrivateKey
}
// ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension.
type ServicePrincipalMSISecret struct {
}
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
return nil
}
// SignJwt returns the JWT signed with the certificate's private key.
func (secret *ServicePrincipalCertificateSecret) SignJwt(spt *ServicePrincipalToken) (string, error) {
hasher := sha1.New()
@ -173,7 +191,7 @@ type ServicePrincipalToken struct {
resource string
autoRefresh bool
refreshWithin time.Duration
sender autorest.Sender
sender Sender
refreshCallbacks []TokenRefreshCallback
}
@ -238,10 +256,58 @@ func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID s
)
}
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
func GetMSIVMEndpoint() (string, error) {
return getMSIVMEndpoint(msiPath)
}
func getMSIVMEndpoint(path string) (string, error) {
// Read MSI settings
bytes, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
msiSettings := struct {
URL string `json:"url"`
}{}
err = json.Unmarshal(bytes, &msiSettings)
if err != nil {
return "", err
}
return msiSettings.URL, nil
}
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
// We set the oauth config token endpoint to be MSI's endpoint
msiEndpointURL, err := url.Parse(msiEndpoint)
if err != nil {
return nil, err
}
oauthConfig, err := NewOAuthConfig(msiEndpointURL.String(), "")
if err != nil {
return nil, err
}
spt := &ServicePrincipalToken{
oauthConfig: *oauthConfig,
secret: &ServicePrincipalMSISecret{},
resource: resource,
autoRefresh: true,
refreshWithin: defaultRefresh,
sender: &http.Client{},
refreshCallbacks: callbacks,
}
return spt, nil
}
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
// RefreshWithin).
// RefreshWithin) and autoRefresh flag is on.
func (spt *ServicePrincipalToken) EnsureFresh() error {
if spt.WillExpireIn(spt.refreshWithin) {
if spt.autoRefresh && spt.WillExpireIn(spt.refreshWithin) {
return spt.Refresh()
}
return nil
@ -253,8 +319,7 @@ func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
for _, callback := range spt.refreshCallbacks {
err := callback(spt.Token)
if err != nil {
return autorest.NewErrorWithError(err,
"azure.ServicePrincipalToken", "InvokeRefreshCallbacks", nil, "A TokenRefreshCallback handler returned an error")
return fmt.Errorf("adal: TokenRefreshCallback handler failed. Error = '%v'", err)
}
}
}
@ -287,39 +352,48 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error {
}
}
req, _ := autorest.Prepare(&http.Request{},
autorest.AsPost(),
autorest.AsFormURLEncoded(),
autorest.WithBaseURL(spt.oauthConfig.TokenEndpoint.String()),
autorest.WithFormData(v))
resp, err := autorest.SendWithSender(spt.sender, req)
s := v.Encode()
body := ioutil.NopCloser(strings.NewReader(s))
req, err := http.NewRequest(http.MethodPost, spt.oauthConfig.TokenEndpoint.String(), body)
if err != nil {
return autorest.NewErrorWithError(err,
"azure.ServicePrincipalToken", "Refresh", resp, "Failure sending request for Service Principal %s",
spt.clientID)
return fmt.Errorf("adal: Failed to build the refresh request. Error = '%v'", err)
}
var newToken Token
err = autorest.Respond(resp,
autorest.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&newToken),
autorest.ByClosing())
req.ContentLength = int64(len(s))
req.Header.Set(contentType, mimeTypeFormPost)
if _, ok := spt.secret.(*ServicePrincipalMSISecret); ok {
req.Header.Set(metadataHeader, "true")
}
resp, err := spt.sender.Do(req)
if err != nil {
return autorest.NewErrorWithError(err,
"azure.ServicePrincipalToken", "Refresh", resp, "Failure handling response to Service Principal %s request",
spt.clientID)
return fmt.Errorf("adal: Failed to execute the refresh request. Error = '%v'", err)
}
spt.Token = newToken
defer resp.Body.Close()
rb, err := ioutil.ReadAll(resp.Body)
err = spt.InvokeRefreshCallbacks(newToken)
if err != nil {
// its already wrapped inside InvokeRefreshCallbacks
return err
if resp.StatusCode != http.StatusOK {
if err != nil {
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode)
}
return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb))
}
return nil
if err != nil {
return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err)
}
if len(strings.Trim(string(rb), " ")) == 0 {
return fmt.Errorf("adal: Empty service principal token received during refresh")
}
var token Token
err = json.Unmarshal(rb, &token)
if err != nil {
return fmt.Errorf("adal: Failed to unmarshal the service principal token during refresh. Error = '%v' JSON = '%s'", err, string(rb))
}
spt.Token = token
return spt.InvokeRefreshCallbacks(token)
}
// SetAutoRefresh enables or disables automatic refreshing of stale tokens.
@ -334,30 +408,6 @@ func (spt *ServicePrincipalToken) SetRefreshWithin(d time.Duration) {
return
}
// SetSender sets the autorest.Sender used when obtaining the Service Principal token. An
// SetSender sets the http.Client used when obtaining the Service Principal token. An
// undecorated http.Client is used by default.
func (spt *ServicePrincipalToken) SetSender(s autorest.Sender) {
spt.sender = s
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "Bearer " followed by the AccessToken of the ServicePrincipalToken.
//
// By default, the token will automatically refresh if nearly expired (as determined by the
// RefreshWithin interval). Use the AutoRefresh method to enable or disable automatically refreshing
// tokens.
func (spt *ServicePrincipalToken) WithAuthorization() autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
if spt.autoRefresh {
err := spt.EnsureFresh()
if err != nil {
return r, autorest.NewErrorWithError(err,
"azure.ServicePrincipalToken", "WithAuthorization", nil, "Failed to refresh Service Principal Token for request to %s",
r.URL)
}
}
return (autorest.WithBearerAuthorization(spt.AccessToken)(p)).Prepare(r)
})
}
}
func (spt *ServicePrincipalToken) SetSender(s Sender) { spt.sender = s }

View file

@ -0,0 +1,167 @@
package autorest
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
)
const (
bearerChallengeHeader = "Www-Authenticate"
bearer = "Bearer"
tenantID = "tenantID"
)
// Authorizer is the interface that provides a PrepareDecorator used to supply request
// authorization. Most often, the Authorizer decorator runs last so it has access to the full
// state of the formed HTTP request.
type Authorizer interface {
WithAuthorization() PrepareDecorator
}
// NullAuthorizer implements a default, "do nothing" Authorizer.
type NullAuthorizer struct{}
// WithAuthorization returns a PrepareDecorator that does nothing.
func (na NullAuthorizer) WithAuthorization() PrepareDecorator {
return WithNothing()
}
// BearerAuthorizer implements the bearer authorization
type BearerAuthorizer struct {
tokenProvider adal.OAuthTokenProvider
}
// NewBearerAuthorizer crates a BearerAuthorizer using the given token provider
func NewBearerAuthorizer(tp adal.OAuthTokenProvider) *BearerAuthorizer {
return &BearerAuthorizer{tokenProvider: tp}
}
func (ba *BearerAuthorizer) withBearerAuthorization() PrepareDecorator {
return WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", ba.tokenProvider.OAuthToken()))
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "Bearer " followed by the token.
//
// By default, the token will be automatically refreshed through the Refresher interface.
func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
refresher, ok := ba.tokenProvider.(adal.Refresher)
if ok {
err := refresher.EnsureFresh()
if err != nil {
return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", nil,
"Failed to refresh the Token for request to %s", r.URL)
}
}
return (ba.withBearerAuthorization()(p)).Prepare(r)
})
}
}
// BearerAuthorizerCallbackFunc is the authentication callback signature.
type BearerAuthorizerCallbackFunc func(tenantID, resource string) (*BearerAuthorizer, error)
// BearerAuthorizerCallback implements bearer authorization via a callback.
type BearerAuthorizerCallback struct {
sender Sender
callback BearerAuthorizerCallbackFunc
}
// NewBearerAuthorizerCallback creates a bearer authorization callback. The callback
// is invoked when the HTTP request is submitted.
func NewBearerAuthorizerCallback(sender Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback {
if sender == nil {
sender = &http.Client{}
}
return &BearerAuthorizerCallback{sender: sender, callback: callback}
}
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose value
// is "Bearer " followed by the token. The BearerAuthorizer is obtained via a user-supplied callback.
//
// By default, the token will be automatically refreshed through the Refresher interface.
func (bacb *BearerAuthorizerCallback) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
// make a copy of the request and remove the body as it's not
// required and avoids us having to create a copy of it.
rCopy := *r
removeRequestBody(&rCopy)
resp, err := bacb.sender.Do(&rCopy)
if err == nil && resp.StatusCode == 401 {
defer resp.Body.Close()
if hasBearerChallenge(resp) {
bc, err := newBearerChallenge(resp)
if err != nil {
return r, err
}
if bacb.callback != nil {
ba, err := bacb.callback(bc.values[tenantID], bc.values["resource"])
if err != nil {
return r, err
}
return ba.WithAuthorization()(p).Prepare(r)
}
}
}
return r, err
})
}
}
// returns true if the HTTP response contains a bearer challenge
func hasBearerChallenge(resp *http.Response) bool {
authHeader := resp.Header.Get(bearerChallengeHeader)
if len(authHeader) == 0 || strings.Index(authHeader, bearer) < 0 {
return false
}
return true
}
type bearerChallenge struct {
values map[string]string
}
func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) {
challenge := strings.TrimSpace(resp.Header.Get(bearerChallengeHeader))
trimmedChallenge := challenge[len(bearer)+1:]
// challenge is a set of key=value pairs that are comma delimited
pairs := strings.Split(trimmedChallenge, ",")
if len(pairs) < 1 {
err = fmt.Errorf("challenge '%s' contains no pairs", challenge)
return bc, err
}
bc.values = make(map[string]string)
for i := range pairs {
trimmedPair := strings.TrimSpace(pairs[i])
pair := strings.Split(trimmedPair, "=")
if len(pair) == 2 {
// remove the enclosing quotes
key := strings.Trim(pair[0], "\"")
value := strings.Trim(pair[1], "\"")
switch key {
case "authorization", "authorization_uri":
// strip the tenant ID from the authorization URL
asURL, err := url.Parse(value)
if err != nil {
return bc, err
}
bc.values[tenantID] = asURL.Path[1:]
default:
bc.values[key] = value
}
}
}
return bc, err
}

View file

@ -17,12 +17,6 @@ const (
)
const (
methodDelete = "DELETE"
methodPatch = "PATCH"
methodPost = "POST"
methodPut = "PUT"
methodGet = "GET"
operationInProgress string = "InProgress"
operationCanceled string = "Canceled"
operationFailed string = "Failed"
@ -226,7 +220,7 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
// Lastly, requests against an existing resource, use the last request URI
if ps.uri == "" {
m := strings.ToUpper(req.Method)
if m == methodPatch || m == methodPut || m == methodGet {
if m == http.MethodPatch || m == http.MethodPut || m == http.MethodGet {
ps.uri = req.URL.String()
}
}

View file

@ -1,13 +0,0 @@
package azure
import (
"net/url"
)
// OAuthConfig represents the endpoints needed
// in OAuth operations
type OAuthConfig struct {
AuthorizeEndpoint url.URL
TokenEndpoint url.URL
DeviceCodeEndpoint url.URL
}

View file

@ -2,14 +2,9 @@ package azure
import (
"fmt"
"net/url"
"strings"
)
const (
activeDirectoryAPIVersion = "1.0"
)
var environments = map[string]Environment{
"AZURECHINACLOUD": ChinaCloud,
"AZUREGERMANCLOUD": GermanCloud,
@ -133,35 +128,3 @@ func EnvironmentFromName(name string) (Environment, error) {
}
return env, nil
}
// OAuthConfigForTenant returns an OAuthConfig with tenant specific urls
func (env Environment) OAuthConfigForTenant(tenantID string) (*OAuthConfig, error) {
return OAuthConfigForTenant(env.ActiveDirectoryEndpoint, tenantID)
}
// OAuthConfigForTenant returns an OAuthConfig with tenant specific urls for target cloud auth endpoint
func OAuthConfigForTenant(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
template := "%s/oauth2/%s?api-version=%s"
u, err := url.Parse(activeDirectoryEndpoint)
if err != nil {
return nil, err
}
authorizeURL, err := u.Parse(fmt.Sprintf(template, tenantID, "authorize", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
tokenURL, err := u.Parse(fmt.Sprintf(template, tenantID, "token", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
deviceCodeURL, err := u.Parse(fmt.Sprintf(template, tenantID, "devicecode", activeDirectoryAPIVersion))
if err != nil {
return nil, err
}
return &OAuthConfig{
AuthorizeEndpoint: *authorizeURL,
TokenEndpoint: *tokenURL,
DeviceCodeEndpoint: *deviceCodeURL,
}, nil
}

View file

@ -35,6 +35,7 @@ var (
statusCodesForRetry = []int{
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503

View file

@ -0,0 +1,109 @@
package date
import (
"bytes"
"encoding/binary"
"encoding/json"
"time"
)
// unixEpoch is the moment in time that should be treated as timestamp 0.
var unixEpoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
// UnixTime marshals and unmarshals a time that is represented as the number
// of seconds (ignoring skip-seconds) since the Unix Epoch.
type UnixTime time.Time
// Duration returns the time as a Duration since the UnixEpoch.
func (t UnixTime) Duration() time.Duration {
return time.Time(t).Sub(unixEpoch)
}
// NewUnixTimeFromSeconds creates a UnixTime as a number of seconds from the UnixEpoch.
func NewUnixTimeFromSeconds(seconds float64) UnixTime {
return NewUnixTimeFromDuration(time.Duration(seconds * float64(time.Second)))
}
// NewUnixTimeFromNanoseconds creates a UnixTime as a number of nanoseconds from the UnixEpoch.
func NewUnixTimeFromNanoseconds(nanoseconds int64) UnixTime {
return NewUnixTimeFromDuration(time.Duration(nanoseconds))
}
// NewUnixTimeFromDuration creates a UnixTime as a duration of time since the UnixEpoch.
func NewUnixTimeFromDuration(dur time.Duration) UnixTime {
return UnixTime(unixEpoch.Add(dur))
}
// UnixEpoch retreives the moment considered the Unix Epoch. I.e. The time represented by '0'
func UnixEpoch() time.Time {
return unixEpoch
}
// MarshalJSON preserves the UnixTime as a JSON number conforming to Unix Timestamp requirements.
// (i.e. the number of seconds since midnight January 1st, 1970 not considering leap seconds.)
func (t UnixTime) MarshalJSON() ([]byte, error) {
buffer := &bytes.Buffer{}
enc := json.NewEncoder(buffer)
err := enc.Encode(float64(time.Time(t).UnixNano()) / 1e9)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// UnmarshalJSON reconstitures a UnixTime saved as a JSON number of the number of seconds since
// midnight January 1st, 1970.
func (t *UnixTime) UnmarshalJSON(text []byte) error {
dec := json.NewDecoder(bytes.NewReader(text))
var secondsSinceEpoch float64
if err := dec.Decode(&secondsSinceEpoch); err != nil {
return err
}
*t = NewUnixTimeFromSeconds(secondsSinceEpoch)
return nil
}
// MarshalText stores the number of seconds since the Unix Epoch as a textual floating point number.
func (t UnixTime) MarshalText() ([]byte, error) {
cast := time.Time(t)
return cast.MarshalText()
}
// UnmarshalText populates a UnixTime with a value stored textually as a floating point number of seconds since the Unix Epoch.
func (t *UnixTime) UnmarshalText(raw []byte) error {
var unmarshaled time.Time
if err := unmarshaled.UnmarshalText(raw); err != nil {
return err
}
*t = UnixTime(unmarshaled)
return nil
}
// MarshalBinary converts a UnixTime into a binary.LittleEndian float64 of nanoseconds since the epoch.
func (t UnixTime) MarshalBinary() ([]byte, error) {
buf := &bytes.Buffer{}
payload := int64(t.Duration())
if err := binary.Write(buf, binary.LittleEndian, &payload); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// UnmarshalBinary converts a from a binary.LittleEndian float64 of nanoseconds since the epoch into a UnixTime.
func (t *UnixTime) UnmarshalBinary(raw []byte) error {
var nanosecondsSinceEpoch int64
if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, &nanosecondsSinceEpoch); err != nil {
return err
}
*t = NewUnixTimeFromNanoseconds(nanosecondsSinceEpoch)
return nil
}

View file

@ -31,6 +31,9 @@ type DetailedError struct {
// Service Error is the response body of failed API in bytes
ServiceError []byte
// Response is the response object that was returned during failure if applicable.
Response *http.Response
}
// NewError creates a new Error conforming object from the passed packageType, method, and
@ -67,6 +70,7 @@ func NewErrorWithError(original error, packageType string, method string, resp *
Method: method,
StatusCode: statusCode,
Message: fmt.Sprintf(message, args...),
Response: resp,
}
}

View file

@ -426,18 +426,3 @@ func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorato
})
}
}
// Authorizer is the interface that provides a PrepareDecorator used to supply request
// authorization. Most often, the Authorizer decorator runs last so it has access to the full
// state of the formed HTTP request.
type Authorizer interface {
WithAuthorization() PrepareDecorator
}
// NullAuthorizer implements a default, "do nothing" Authorizer.
type NullAuthorizer struct{}
// WithAuthorization returns a PrepareDecorator that does nothing.
func (na NullAuthorizer) WithAuthorization() PrepareDecorator {
return WithNothing()
}

View file

@ -0,0 +1,38 @@
package autorest
import (
"bytes"
"io"
"io/ioutil"
"net/http"
)
// NewRetriableRequest returns a wrapper around an HTTP request that support retry logic.
func NewRetriableRequest(req *http.Request) *RetriableRequest {
return &RetriableRequest{req: req}
}
// Request returns the wrapped HTTP request.
func (rr *RetriableRequest) Request() *http.Request {
return rr.req
}
func (rr *RetriableRequest) prepareFromByteReader() (err error) {
// fall back to making a copy (only do this once)
b := []byte{}
if rr.req.ContentLength > 0 {
b = make([]byte, rr.req.ContentLength)
_, err = io.ReadFull(rr.req.Body, b)
if err != nil {
return err
}
} else {
b, err = ioutil.ReadAll(rr.req.Body)
if err != nil {
return err
}
}
rr.br = bytes.NewReader(b)
rr.req.Body = ioutil.NopCloser(rr.br)
return err
}

View file

@ -0,0 +1,44 @@
// +build !go1.8
package autorest
import (
"bytes"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
br *bytes.Reader
reset bool
}
// Prepare signals that the request is about to be sent.
func (rr *RetriableRequest) Prepare() (err error) {
// preserve the request body; this is to support retry logic as
// the underlying transport will always close the reqeust body
if rr.req.Body != nil {
if rr.reset {
if rr.br != nil {
_, err = rr.br.Seek(0, 0 /*io.SeekStart*/)
}
rr.reset = false
if err != nil {
return err
}
}
if rr.br == nil {
// fall back to making a copy (only do this once)
err = rr.prepareFromByteReader()
}
// indicates that the request body needs to be reset
rr.reset = true
}
return err
}
func removeRequestBody(req *http.Request) {
req.Body = nil
req.ContentLength = 0
}

View file

@ -0,0 +1,56 @@
// +build go1.8
package autorest
import (
"bytes"
"io"
"net/http"
)
// RetriableRequest provides facilities for retrying an HTTP request.
type RetriableRequest struct {
req *http.Request
rc io.ReadCloser
br *bytes.Reader
reset bool
}
// Prepare signals that the request is about to be sent.
func (rr *RetriableRequest) Prepare() (err error) {
// preserve the request body; this is to support retry logic as
// the underlying transport will always close the reqeust body
if rr.req.Body != nil {
if rr.reset {
if rr.rc != nil {
rr.req.Body = rr.rc
} else if rr.br != nil {
_, err = rr.br.Seek(0, io.SeekStart)
}
rr.reset = false
if err != nil {
return err
}
}
if rr.req.GetBody != nil {
// this will allow us to preserve the body without having to
// make a copy. note we need to do this on each iteration
rr.rc, err = rr.req.GetBody()
if err != nil {
return err
}
} else if rr.br == nil {
// fall back to making a copy (only do this once)
err = rr.prepareFromByteReader()
}
// indicates that the request body needs to be reset
rr.reset = true
}
return err
}
func removeRequestBody(req *http.Request) {
req.Body = nil
req.GetBody = nil
req.ContentLength = 0
}

View file

@ -1,12 +1,11 @@
package autorest
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"math"
"net/http"
"strconv"
"time"
)
@ -175,8 +174,13 @@ func DoPollForStatusCodes(duration time.Duration, delay time.Duration, codes ...
func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
rr := NewRetriableRequest(r)
for attempt := 0; attempt < attempts; attempt++ {
resp, err = s.Do(r)
err = rr.Prepare()
if err != nil {
return resp, err
}
resp, err = s.Do(rr.Request())
if err == nil {
return resp, err
}
@ -194,29 +198,43 @@ func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
b := []byte{}
if r.Body != nil {
b, err = ioutil.ReadAll(r.Body)
if err != nil {
return resp, err
}
}
rr := NewRetriableRequest(r)
// Increment to add the first call (attempts denotes number of retries)
attempts++
for attempt := 0; attempt < attempts; attempt++ {
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
resp, err = s.Do(r)
err = rr.Prepare()
if err != nil {
return resp, err
}
resp, err = s.Do(rr.Request())
if err != nil || !ResponseHasStatusCode(resp, codes...) {
return resp, err
}
DelayForBackoff(backoff, attempt, r.Cancel)
delayed := DelayWithRetryAfter(resp, r.Cancel)
if !delayed {
DelayForBackoff(backoff, attempt, r.Cancel)
}
}
return resp, err
})
}
}
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in
// responses with status code 429
func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool {
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
select {
case <-time.After(time.Duration(retryAfter) * time.Second):
return true
case <-cancel:
return false
}
}
return false
}
// DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal
// to or greater than the specified duration, exponentially backing off between requests using the
// supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the
@ -224,9 +242,14 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
func DoRetryForDuration(d time.Duration, backoff time.Duration) SendDecorator {
return func(s Sender) Sender {
return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
rr := NewRetriableRequest(r)
end := time.Now().Add(d)
for attempt := 0; time.Now().Before(end); attempt++ {
resp, err = s.Do(r)
err = rr.Prepare()
if err != nil {
return resp, err
}
resp, err = s.Do(rr.Request())
if err == nil {
return resp, err
}

View file

@ -1,29 +1,35 @@
package autorest
import (
"bytes"
"fmt"
"strings"
"sync"
)
const (
major = 7
minor = 3
patch = 1
major = 8
minor = 0
patch = 0
tag = ""
)
var versionLock sync.Once
var once sync.Once
var version string
// Version returns the semantic version (see http://semver.org).
func Version() string {
versionLock.Do(func() {
version = fmt.Sprintf("v%d.%d.%d", major, minor, patch)
if trimmed := strings.TrimPrefix(tag, "-"); trimmed != "" {
version = fmt.Sprintf("%s-%s", version, trimmed)
once.Do(func() {
semver := fmt.Sprintf("%d.%d.%d", major, minor, patch)
verBuilder := bytes.NewBufferString(semver)
if tag != "" && tag != "-" {
updated := strings.TrimPrefix(tag, "-")
_, err := verBuilder.WriteString("-" + updated)
if err == nil {
verBuilder = bytes.NewBufferString(semver)
}
}
version = verBuilder.String()
})
return version
}