Update Azure SDK with release v16.2.1 Update Azure autorest SDK with release v10.8.1 Signed-off-by: Yu Wang <yuwa@microsoft.com>
This commit is contained in:
parent
83389a1480
commit
62797237b9
79 changed files with 12075 additions and 2797 deletions
292
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
Normal file
292
vendor/github.com/Azure/go-autorest/autorest/adal/README.md
generated
vendored
Normal file
|
@ -0,0 +1,292 @@
|
|||
# Azure Active Directory authentication for Go
|
||||
|
||||
This is a standalone package for authenticating with Azure Active
|
||||
Directory from other Go libraries and applications, in particular the [Azure SDK
|
||||
for Go](https://github.com/Azure/azure-sdk-for-go).
|
||||
|
||||
Note: Despite the package's name it is not related to other "ADAL" libraries
|
||||
maintained in the [github.com/AzureAD](https://github.com/AzureAD) org. Issues
|
||||
should be opened in [this repo's](https://github.com/Azure/go-autorest/issues)
|
||||
or [the SDK's](https://github.com/Azure/azure-sdk-for-go/issues) issue
|
||||
trackers.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get -u github.com/Azure/go-autorest/autorest/adal
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli).
|
||||
|
||||
### Register an Azure AD Application with secret
|
||||
|
||||
|
||||
1. Register a new application with a `secret` credential
|
||||
|
||||
```
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--password secret
|
||||
```
|
||||
|
||||
2. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "Application ID"
|
||||
```
|
||||
|
||||
* Replace `Application ID` with `appId` from step 1.
|
||||
|
||||
### Register an Azure AD Application with certificate
|
||||
|
||||
1. Create a private key
|
||||
|
||||
```
|
||||
openssl genrsa -out "example-app.key" 2048
|
||||
```
|
||||
|
||||
2. Create the certificate
|
||||
|
||||
```
|
||||
openssl req -new -key "example-app.key" -subj "/CN=example-app" -out "example-app.csr"
|
||||
openssl x509 -req -in "example-app.csr" -signkey "example-app.key" -out "example-app.crt" -days 10000
|
||||
```
|
||||
|
||||
3. Create the PKCS12 version of the certificate containing also the private key
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -out "example-app.pfx" -inkey "example-app.key" -in "example-app.crt" -passout pass:
|
||||
|
||||
```
|
||||
|
||||
4. Register a new application with the certificate content form `example-app.crt`
|
||||
|
||||
```
|
||||
certificateContents="$(tail -n+2 "example-app.crt" | head -n-1)"
|
||||
|
||||
az ad app create \
|
||||
--display-name example-app \
|
||||
--homepage https://example-app/home \
|
||||
--identifier-uris https://example-app/app \
|
||||
--key-usage Verify --end-date 2018-01-01 \
|
||||
--key-value "${certificateContents}"
|
||||
```
|
||||
|
||||
5. Create a service principal using the `Application ID` from previous step
|
||||
|
||||
```
|
||||
az ad sp create --id "APPLICATION_ID"
|
||||
```
|
||||
|
||||
* Replace `APPLICATION_ID` with `appId` from step 4.
|
||||
|
||||
|
||||
### Grant the necessary permissions
|
||||
|
||||
Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained
|
||||
level. There is a set of [pre-defined roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-built-in-roles)
|
||||
which can be assigned to a service principal of an Azure AD application depending of your needs.
|
||||
|
||||
```
|
||||
az role assignment create --assigner "SERVICE_PRINCIPAL_ID" --role "ROLE_NAME"
|
||||
```
|
||||
|
||||
* Replace the `SERVICE_PRINCIPAL_ID` with the `appId` from previous step.
|
||||
* Replace the `ROLE_NAME` with a role name of your choice.
|
||||
|
||||
It is also possible to define custom role definitions.
|
||||
|
||||
```
|
||||
az role definition create --role-definition role-definition.json
|
||||
```
|
||||
|
||||
* Check [custom roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file.
|
||||
|
||||
|
||||
### Acquire Access Token
|
||||
|
||||
The common configuration used by all flows:
|
||||
|
||||
```Go
|
||||
const activeDirectoryEndpoint = "https://login.microsoftonline.com/"
|
||||
tenantID := "TENANT_ID"
|
||||
oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID)
|
||||
|
||||
applicationID := "APPLICATION_ID"
|
||||
|
||||
callback := func(token adal.Token) error {
|
||||
// This is called after the token is acquired
|
||||
}
|
||||
|
||||
// The resource for which the token is acquired
|
||||
resource := "https://management.core.windows.net/"
|
||||
```
|
||||
|
||||
* Replace the `TENANT_ID` with your tenant ID.
|
||||
* Replace the `APPLICATION_ID` with the value from previous section.
|
||||
|
||||
#### Client Credentials
|
||||
|
||||
```Go
|
||||
applicationSecret := "APPLICATION_SECRET"
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(
|
||||
oauthConfig,
|
||||
appliationID,
|
||||
applicationSecret,
|
||||
resource,
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Replace the `APPLICATION_SECRET` with the `password` value from previous section.
|
||||
|
||||
#### Client Certificate
|
||||
|
||||
```Go
|
||||
certificatePath := "./example-app.pfx"
|
||||
|
||||
certData, err := ioutil.ReadFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err)
|
||||
}
|
||||
|
||||
// Get the certificate and private key from pfx file
|
||||
certificate, rsaPrivateKey, err := decodePkcs12(certData, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromCertificate(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
certificate,
|
||||
rsaPrivateKey,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
// Acquire a new access token
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
* Update the certificate path to point to the example-app.pfx file which was created in previous section.
|
||||
|
||||
|
||||
#### Device Code
|
||||
|
||||
```Go
|
||||
oauthClient := &http.Client{}
|
||||
|
||||
// Acquire the device code
|
||||
deviceCode, err := adal.InitiateDeviceAuth(
|
||||
oauthClient,
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to start device auth flow: %s", err)
|
||||
}
|
||||
|
||||
// Display the authentication message
|
||||
fmt.Println(*deviceCode.Message)
|
||||
|
||||
// Wait here until the user is authenticated
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to finish device auth flow: %s", err)
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
resource,
|
||||
*token,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Username password authenticate
|
||||
|
||||
```Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromUsernamePassword(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
username,
|
||||
password,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
#### Authorization code authenticate
|
||||
|
||||
``` Go
|
||||
spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode(
|
||||
oauthConfig,
|
||||
applicationID,
|
||||
clientSecret,
|
||||
authorizationCode,
|
||||
redirectURI,
|
||||
resource,
|
||||
callbacks...)
|
||||
|
||||
err = spt.Refresh()
|
||||
if (err == nil) {
|
||||
token := spt.Token
|
||||
}
|
||||
```
|
||||
|
||||
### Command Line Tool
|
||||
|
||||
A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above.
|
||||
|
||||
```
|
||||
adal -h
|
||||
|
||||
Usage of ./adal:
|
||||
-applicationId string
|
||||
application id
|
||||
-certificatePath string
|
||||
path to pk12/PFC application certificate
|
||||
-mode string
|
||||
authentication mode (device, secret, cert, refresh) (default "device")
|
||||
-resource string
|
||||
resource for which the token is requested
|
||||
-secret string
|
||||
application secret
|
||||
-tenantId string
|
||||
tenant id
|
||||
-tokenCachePath string
|
||||
location of oath token cache (default "/home/cgc/.adal/accessToken.json")
|
||||
```
|
||||
|
||||
Example acquire a token for `https://management.core.windows.net/` using device code flow:
|
||||
|
||||
```
|
||||
adal -mode device \
|
||||
-applicationId "APPLICATION_ID" \
|
||||
-tenantId "TENANT_ID" \
|
||||
-resource https://management.core.windows.net/
|
||||
|
||||
```
|
81
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
Normal file
81
vendor/github.com/Azure/go-autorest/autorest/adal/config.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// IsZero returns true if the OAuthConfig object is zero-initialized.
|
||||
func (oac OAuthConfig) IsZero() bool {
|
||||
return oac == OAuthConfig{}
|
||||
}
|
||||
|
||||
func validateStringParam(param, name string) error {
|
||||
if len(param) == 0 {
|
||||
return fmt.Errorf("parameter '" + name + "' cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewOAuthConfig returns an OAuthConfig with tenant specific urls
|
||||
func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) {
|
||||
if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// it's legal for tenantID to be empty so don't validate it
|
||||
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
|
||||
}
|
242
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
Normal file
242
vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go
generated
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
This file is largely based on rjw57/oauth2device's code, with the follow differences:
|
||||
* scope -> resource, and only allow a single one
|
||||
* receive "Message" in the DeviceCode struct and show it to users as the prompt
|
||||
* azure-xplat-cli has the following behavior that this emulates:
|
||||
- does not send client_secret during the token exchange
|
||||
- sends resource again in the token exchange request
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
logPrefix = "autorest/adal/devicetoken:"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDeviceGeneric represents an unknown error from the token endpoint when using device flow
|
||||
ErrDeviceGeneric = fmt.Errorf("%s Error while retrieving OAuth token: Unknown Error", logPrefix)
|
||||
|
||||
// ErrDeviceAccessDenied represents an access denied error from the token endpoint when using device flow
|
||||
ErrDeviceAccessDenied = fmt.Errorf("%s Error while retrieving OAuth token: Access Denied", logPrefix)
|
||||
|
||||
// ErrDeviceAuthorizationPending represents the server waiting on the user to complete the device flow
|
||||
ErrDeviceAuthorizationPending = fmt.Errorf("%s Error while retrieving OAuth token: Authorization Pending", logPrefix)
|
||||
|
||||
// ErrDeviceCodeExpired represents the server timing out and expiring the code during device flow
|
||||
ErrDeviceCodeExpired = fmt.Errorf("%s Error while retrieving OAuth token: Code Expired", logPrefix)
|
||||
|
||||
// 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
|
||||
// It contains information to instruct the user to complete the auth flow
|
||||
type DeviceCode struct {
|
||||
DeviceCode *string `json:"device_code,omitempty"`
|
||||
UserCode *string `json:"user_code,omitempty"`
|
||||
VerificationURL *string `json:"verification_url,omitempty"`
|
||||
ExpiresIn *int64 `json:"expires_in,string,omitempty"`
|
||||
Interval *int64 `json:"interval,string,omitempty"`
|
||||
|
||||
Message *string `json:"message"` // Azure specific
|
||||
Resource string // store the following, stored when initiating, used when exchanging
|
||||
OAuthConfig OAuthConfig
|
||||
ClientID string
|
||||
}
|
||||
|
||||
// TokenError is the object returned by the token exchange endpoint
|
||||
// when something is amiss
|
||||
type TokenError struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
ErrorCodes []int `json:"error_codes,omitempty"`
|
||||
ErrorDescription *string `json:"error_description,omitempty"`
|
||||
Timestamp *string `json:"timestamp,omitempty"`
|
||||
TraceID *string `json:"trace_id,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceToken is the object return by the token exchange endpoint
|
||||
// It can either look like a Token or an ErrorToken, so put both here
|
||||
// and check for presence of "Error" to know if we are in error state
|
||||
type deviceToken struct {
|
||||
Token
|
||||
TokenError
|
||||
}
|
||||
|
||||
// InitiateDeviceAuth initiates a device auth flow. It returns a DeviceCode
|
||||
// that can be used with CheckForUserCompletion or WaitForUserCompletion.
|
||||
func InitiateDeviceAuth(sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) {
|
||||
v := url.Values{
|
||||
"client_id": []string{clientID},
|
||||
"resource": []string{resource},
|
||||
}
|
||||
|
||||
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.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 = json.Unmarshal(rb, &code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
code.ClientID = clientID
|
||||
code.Resource = resource
|
||||
code.OAuthConfig = oauthConfig
|
||||
|
||||
return &code, nil
|
||||
}
|
||||
|
||||
// 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(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},
|
||||
}
|
||||
|
||||
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.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 = json.Unmarshal(rb, &token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error())
|
||||
}
|
||||
|
||||
if token.Error == nil {
|
||||
return &token.Token, nil
|
||||
}
|
||||
|
||||
switch *token.Error {
|
||||
case "authorization_pending":
|
||||
return nil, ErrDeviceAuthorizationPending
|
||||
case "slow_down":
|
||||
return nil, ErrDeviceSlowDown
|
||||
case "access_denied":
|
||||
return nil, ErrDeviceAccessDenied
|
||||
case "code_expired":
|
||||
return nil, ErrDeviceCodeExpired
|
||||
default:
|
||||
return nil, ErrDeviceGeneric
|
||||
}
|
||||
}
|
||||
|
||||
// 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(sender Sender, code *DeviceCode) (*Token, error) {
|
||||
intervalDuration := time.Duration(*code.Interval) * time.Second
|
||||
waitDuration := intervalDuration
|
||||
|
||||
for {
|
||||
token, err := CheckForUserCompletion(sender, code)
|
||||
|
||||
if err == nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
switch err {
|
||||
case ErrDeviceSlowDown:
|
||||
waitDuration += waitDuration
|
||||
case ErrDeviceAuthorizationPending:
|
||||
// noop
|
||||
default: // everything else is "fatal" to us
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if waitDuration > (intervalDuration * 3) {
|
||||
return nil, fmt.Errorf("%s Error waiting for user to complete device flow. Server told us to slow_down too much", logPrefix)
|
||||
}
|
||||
|
||||
time.Sleep(waitDuration)
|
||||
}
|
||||
}
|
73
vendor/github.com/Azure/go-autorest/autorest/adal/persist.go
generated
vendored
Normal file
73
vendor/github.com/Azure/go-autorest/autorest/adal/persist.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// LoadToken restores a Token object from a file located at 'path'.
|
||||
func LoadToken(path string) (*Token, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var token Token
|
||||
|
||||
dec := json.NewDecoder(file)
|
||||
if err = dec.Decode(&token); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode contents of file (%s) into Token representation: %v", path, err)
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// SaveToken persists an oauth token at the given location on disk.
|
||||
// It moves the new file into place so it can safely be used to replace an existing file
|
||||
// that maybe accessed by multiple processes.
|
||||
func SaveToken(path string, mode os.FileMode, token Token) error {
|
||||
dir := filepath.Dir(path)
|
||||
err := os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory (%s) to store token in: %v", dir, err)
|
||||
}
|
||||
|
||||
newFile, err := ioutil.TempFile(dir, "token")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the temp file to write the token: %v", err)
|
||||
}
|
||||
tempPath := newFile.Name()
|
||||
|
||||
if err := json.NewEncoder(newFile).Encode(token); err != nil {
|
||||
return fmt.Errorf("failed to encode token to file (%s) while saving token: %v", tempPath, err)
|
||||
}
|
||||
if err := newFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close temp file %s: %v", tempPath, err)
|
||||
}
|
||||
|
||||
// Atomic replace to avoid multi-writer file corruptions
|
||||
if err := os.Rename(tempPath, path); err != nil {
|
||||
return fmt.Errorf("failed to move temporary token to desired output location. src=%s dst=%s: %v", tempPath, path, err)
|
||||
}
|
||||
if err := os.Chmod(path, mode); err != nil {
|
||||
return fmt.Errorf("failed to chmod the token file %s: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
60
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
Normal file
60
vendor/github.com/Azure/go-autorest/autorest/adal/sender.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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
|
||||
}
|
795
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
Normal file
795
vendor/github.com/Azure/go-autorest/autorest/adal/token.go
generated
vendored
Normal file
|
@ -0,0 +1,795 @@
|
|||
package adal
|
||||
|
||||
// Copyright 2017 Microsoft Corporation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/date"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRefresh = 5 * time.Minute
|
||||
|
||||
// OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow
|
||||
OAuthGrantTypeDeviceCode = "device_code"
|
||||
|
||||
// OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows
|
||||
OAuthGrantTypeClientCredentials = "client_credentials"
|
||||
|
||||
// OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows
|
||||
OAuthGrantTypeUserPass = "password"
|
||||
|
||||
// OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows
|
||||
OAuthGrantTypeRefreshToken = "refresh_token"
|
||||
|
||||
// OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows
|
||||
OAuthGrantTypeAuthorizationCode = "authorization_code"
|
||||
|
||||
// metadataHeader is the header required by MSI extension
|
||||
metadataHeader = "Metadata"
|
||||
|
||||
// msiEndpoint is the well known endpoint for getting MSI authentications tokens
|
||||
msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
|
||||
)
|
||||
|
||||
// OAuthTokenProvider is an interface which should be implemented by an access token retriever
|
||||
type OAuthTokenProvider interface {
|
||||
OAuthToken() string
|
||||
}
|
||||
|
||||
// TokenRefreshError is an interface used by errors returned during token refresh.
|
||||
type TokenRefreshError interface {
|
||||
error
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
// Refresher is an interface for token refresh functionality
|
||||
type Refresher interface {
|
||||
Refresh() error
|
||||
RefreshExchange(resource string) error
|
||||
EnsureFresh() error
|
||||
}
|
||||
|
||||
// RefresherWithContext is an interface for token refresh functionality
|
||||
type RefresherWithContext interface {
|
||||
RefreshWithContext(ctx context.Context) error
|
||||
RefreshExchangeWithContext(ctx context.Context, resource string) error
|
||||
EnsureFreshWithContext(ctx context.Context) error
|
||||
}
|
||||
|
||||
// TokenRefreshCallback is the type representing callbacks that will be called after
|
||||
// a successful token refresh
|
||||
type TokenRefreshCallback func(Token) error
|
||||
|
||||
// Token encapsulates the access token used to authorize Azure requests.
|
||||
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"`
|
||||
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"token_type"`
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
s = -3600
|
||||
}
|
||||
|
||||
expiration := date.NewUnixTimeFromSeconds(float64(s))
|
||||
|
||||
return time.Time(expiration).UTC()
|
||||
}
|
||||
|
||||
// IsExpired returns true if the Token is expired, false otherwise.
|
||||
func (t Token) IsExpired() bool {
|
||||
return t.WillExpireIn(0)
|
||||
}
|
||||
|
||||
// WillExpireIn returns true if the Token will expire after the passed time.Duration interval
|
||||
// from now, false otherwise.
|
||||
func (t Token) WillExpireIn(d time.Duration) bool {
|
||||
return !t.Expires().After(time.Now().Add(d))
|
||||
}
|
||||
|
||||
//OAuthToken return the current access token
|
||||
func (t *Token) OAuthToken() string {
|
||||
return t.AccessToken
|
||||
}
|
||||
|
||||
// ServicePrincipalNoSecret represents a secret type that contains no secret
|
||||
// meaning it is not valid for fetching a fresh token. This is used by Manual
|
||||
type ServicePrincipalNoSecret struct {
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret
|
||||
// It only returns an error for the ServicePrincipalNoSecret type
|
||||
func (noSecret *ServicePrincipalNoSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
return fmt.Errorf("Manually created ServicePrincipalToken does not contain secret material to retrieve a new access token")
|
||||
}
|
||||
|
||||
// ServicePrincipalSecret is an interface that allows various secret mechanism to fill the form
|
||||
// that is submitted when acquiring an oAuth token.
|
||||
type ServicePrincipalSecret interface {
|
||||
SetAuthenticationValues(spt *ServicePrincipalToken, values *url.Values) error
|
||||
}
|
||||
|
||||
// ServicePrincipalTokenSecret implements ServicePrincipalSecret for client_secret type authorization.
|
||||
type ServicePrincipalTokenSecret struct {
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
// It will populate the form submitted during oAuth Token Acquisition using the client_secret.
|
||||
func (tokenSecret *ServicePrincipalTokenSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("client_secret", tokenSecret.ClientSecret)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServicePrincipalCertificateSecret implements ServicePrincipalSecret for generic RSA cert auth with signed JWTs.
|
||||
type ServicePrincipalCertificateSecret struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension.
|
||||
type ServicePrincipalMSISecret struct {
|
||||
}
|
||||
|
||||
// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth.
|
||||
type ServicePrincipalUsernamePasswordSecret struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth.
|
||||
type ServicePrincipalAuthorizationCodeSecret struct {
|
||||
ClientSecret string
|
||||
AuthorizationCode string
|
||||
RedirectURI string
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("code", secret.AuthorizationCode)
|
||||
v.Set("client_secret", secret.ClientSecret)
|
||||
v.Set("redirect_uri", secret.RedirectURI)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
v.Set("username", secret.Username)
|
||||
v.Set("password", secret.Password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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()
|
||||
_, err := hasher.Write(secret.Certificate.Raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
thumbprint := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
// The jti (JWT ID) claim provides a unique identifier for the JWT.
|
||||
jti := make([]byte, 20)
|
||||
_, err = rand.Read(jti)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
token.Header["x5t"] = thumbprint
|
||||
token.Claims = jwt.MapClaims{
|
||||
"aud": spt.oauthConfig.TokenEndpoint.String(),
|
||||
"iss": spt.clientID,
|
||||
"sub": spt.clientID,
|
||||
"jti": base64.URLEncoding.EncodeToString(jti),
|
||||
"nbf": time.Now().Unix(),
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
}
|
||||
|
||||
signedString, err := token.SignedString(secret.PrivateKey)
|
||||
return signedString, err
|
||||
}
|
||||
|
||||
// SetAuthenticationValues is a method of the interface ServicePrincipalSecret.
|
||||
// It will populate the form submitted during oAuth Token Acquisition using a JWT signed with a certificate.
|
||||
func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error {
|
||||
jwt, err := secret.SignJwt(spt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Set("client_assertion", jwt)
|
||||
v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServicePrincipalToken encapsulates a Token created for a Service Principal.
|
||||
type ServicePrincipalToken struct {
|
||||
token Token
|
||||
secret ServicePrincipalSecret
|
||||
oauthConfig OAuthConfig
|
||||
clientID string
|
||||
resource string
|
||||
autoRefresh bool
|
||||
refreshLock *sync.RWMutex
|
||||
refreshWithin time.Duration
|
||||
sender Sender
|
||||
|
||||
refreshCallbacks []TokenRefreshCallback
|
||||
}
|
||||
|
||||
func validateOAuthConfig(oac OAuthConfig) error {
|
||||
if oac.IsZero() {
|
||||
return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation.
|
||||
func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(id, "id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("parameter 'secret' cannot be nil")
|
||||
}
|
||||
spt := &ServicePrincipalToken{
|
||||
oauthConfig: oauthConfig,
|
||||
secret: secret,
|
||||
clientID: id,
|
||||
resource: resource,
|
||||
autoRefresh: true,
|
||||
refreshLock: &sync.RWMutex{},
|
||||
refreshWithin: defaultRefresh,
|
||||
sender: &http.Client{},
|
||||
refreshCallbacks: callbacks,
|
||||
}
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token
|
||||
func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token.IsZero() {
|
||||
return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized")
|
||||
}
|
||||
spt, err := NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalNoSecret{},
|
||||
callbacks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.token = token
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal
|
||||
// credentials scoped to the named resource.
|
||||
func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(secret, "secret"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalTokenSecret{
|
||||
ClientSecret: secret,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes.
|
||||
func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if certificate == nil {
|
||||
return nil, fmt.Errorf("parameter 'certificate' cannot be nil")
|
||||
}
|
||||
if privateKey == nil {
|
||||
return nil, fmt.Errorf("parameter 'privateKey' cannot be nil")
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalCertificateSecret{
|
||||
PrivateKey: privateKey,
|
||||
Certificate: certificate,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password.
|
||||
func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(username, "username"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(password, "password"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalUsernamePasswordSecret{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the
|
||||
func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
|
||||
if err := validateOAuthConfig(oauthConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientID, "clientID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(clientSecret, "clientSecret"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(redirectURI, "redirectURI"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewServicePrincipalTokenWithSecret(
|
||||
oauthConfig,
|
||||
clientID,
|
||||
resource,
|
||||
&ServicePrincipalAuthorizationCodeSecret{
|
||||
ClientSecret: clientSecret,
|
||||
AuthorizationCode: authorizationCode,
|
||||
RedirectURI: redirectURI,
|
||||
},
|
||||
callbacks...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines.
|
||||
func GetMSIVMEndpoint() (string, error) {
|
||||
return msiEndpoint, nil
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension.
|
||||
// It will use the system assigned identity when creating the token.
|
||||
func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...)
|
||||
}
|
||||
|
||||
// NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension.
|
||||
// It will use the specified user assigned identity when creating the token.
|
||||
func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...)
|
||||
}
|
||||
|
||||
func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) {
|
||||
if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateStringParam(resource, "resource"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userAssignedID != nil {
|
||||
if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// We set the oauth config token endpoint to be MSI's endpoint
|
||||
msiEndpointURL, err := url.Parse(msiEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("resource", resource)
|
||||
v.Set("api-version", "2018-02-01")
|
||||
if userAssignedID != nil {
|
||||
v.Set("client_id", *userAssignedID)
|
||||
}
|
||||
msiEndpointURL.RawQuery = v.Encode()
|
||||
|
||||
spt := &ServicePrincipalToken{
|
||||
oauthConfig: OAuthConfig{
|
||||
TokenEndpoint: *msiEndpointURL,
|
||||
},
|
||||
secret: &ServicePrincipalMSISecret{},
|
||||
resource: resource,
|
||||
autoRefresh: true,
|
||||
refreshLock: &sync.RWMutex{},
|
||||
refreshWithin: defaultRefresh,
|
||||
sender: &http.Client{},
|
||||
refreshCallbacks: callbacks,
|
||||
}
|
||||
|
||||
if userAssignedID != nil {
|
||||
spt.clientID = *userAssignedID
|
||||
}
|
||||
|
||||
return spt, nil
|
||||
}
|
||||
|
||||
// internal type that implements TokenRefreshError
|
||||
type tokenRefreshError struct {
|
||||
message string
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
// Error implements the error interface which is part of the TokenRefreshError interface.
|
||||
func (tre tokenRefreshError) Error() string {
|
||||
return tre.message
|
||||
}
|
||||
|
||||
// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation.
|
||||
func (tre tokenRefreshError) Response() *http.Response {
|
||||
return tre.resp
|
||||
}
|
||||
|
||||
func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError {
|
||||
return tokenRefreshError{message: message, resp: resp}
|
||||
}
|
||||
|
||||
// EnsureFresh will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFresh() error {
|
||||
return spt.EnsureFreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error {
|
||||
if spt.autoRefresh && spt.token.WillExpireIn(spt.refreshWithin) {
|
||||
// take the write lock then check to see if the token was already refreshed
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
if spt.token.WillExpireIn(spt.refreshWithin) {
|
||||
return spt.refreshInternal(ctx, spt.resource)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InvokeRefreshCallbacks calls any TokenRefreshCallbacks that were added to the SPT during initialization
|
||||
func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error {
|
||||
if spt.refreshCallbacks != nil {
|
||||
for _, callback := range spt.refreshCallbacks {
|
||||
err := callback(spt.token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: TokenRefreshCallback handler failed. Error = '%v'", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) Refresh() error {
|
||||
return spt.RefreshWithContext(context.Background())
|
||||
}
|
||||
|
||||
// RefreshWithContext obtains a fresh token for the Service Principal.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshWithContext(ctx context.Context) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(ctx, spt.resource)
|
||||
}
|
||||
|
||||
// RefreshExchange refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchange(resource string) error {
|
||||
return spt.RefreshExchangeWithContext(context.Background(), resource)
|
||||
}
|
||||
|
||||
// RefreshExchangeWithContext refreshes the token, but for a different resource.
|
||||
// This method is not safe for concurrent use and should be syncrhonized.
|
||||
func (spt *ServicePrincipalToken) RefreshExchangeWithContext(ctx context.Context, resource string) error {
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
return spt.refreshInternal(ctx, resource)
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) getGrantType() string {
|
||||
switch spt.secret.(type) {
|
||||
case *ServicePrincipalUsernamePasswordSecret:
|
||||
return OAuthGrantTypeUserPass
|
||||
case *ServicePrincipalAuthorizationCodeSecret:
|
||||
return OAuthGrantTypeAuthorizationCode
|
||||
default:
|
||||
return OAuthGrantTypeClientCredentials
|
||||
}
|
||||
}
|
||||
|
||||
func isIMDS(u url.URL) bool {
|
||||
imds, err := url.Parse(msiEndpoint)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Host == imds.Host && u.Path == imds.Path
|
||||
}
|
||||
|
||||
func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource string) error {
|
||||
req, err := http.NewRequest(http.MethodPost, spt.oauthConfig.TokenEndpoint.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adal: Failed to build the refresh request. Error = '%v'", err)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if !isIMDS(spt.oauthConfig.TokenEndpoint) {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", spt.clientID)
|
||||
v.Set("resource", resource)
|
||||
|
||||
if spt.token.RefreshToken != "" {
|
||||
v.Set("grant_type", OAuthGrantTypeRefreshToken)
|
||||
v.Set("refresh_token", spt.token.RefreshToken)
|
||||
} else {
|
||||
v.Set("grant_type", spt.getGrantType())
|
||||
err := spt.secret.SetAuthenticationValues(spt, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s := v.Encode()
|
||||
body := ioutil.NopCloser(strings.NewReader(s))
|
||||
req.ContentLength = int64(len(s))
|
||||
req.Header.Set(contentType, mimeTypeFormPost)
|
||||
req.Body = body
|
||||
}
|
||||
|
||||
if _, ok := spt.secret.(*ServicePrincipalMSISecret); ok {
|
||||
req.Method = http.MethodGet
|
||||
req.Header.Set(metadataHeader, "true")
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if isIMDS(spt.oauthConfig.TokenEndpoint) {
|
||||
resp, err = retry(spt.sender, req)
|
||||
} else {
|
||||
resp, err = spt.sender.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Failed to execute the refresh request. Error = '%v'", err), nil)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
rb, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if err != nil {
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body: %v", resp.StatusCode, err), resp)
|
||||
}
|
||||
return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp)
|
||||
}
|
||||
|
||||
// for the following error cases don't return a TokenRefreshError. the operation succeeded
|
||||
// but some transient failure happened during deserialization. by returning a generic error
|
||||
// the retry logic will kick in (we don't retry on TokenRefreshError).
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func retry(sender Sender, req *http.Request) (resp *http.Response, err error) {
|
||||
retries := []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
}
|
||||
// Extra retry status codes requered
|
||||
retries = append(retries, http.StatusNotFound,
|
||||
// all remaining 5xx
|
||||
http.StatusNotImplemented,
|
||||
http.StatusHTTPVersionNotSupported,
|
||||
http.StatusVariantAlsoNegotiates,
|
||||
http.StatusInsufficientStorage,
|
||||
http.StatusLoopDetected,
|
||||
http.StatusNotExtended,
|
||||
http.StatusNetworkAuthenticationRequired)
|
||||
|
||||
attempt := 0
|
||||
maxAttempts := 5
|
||||
|
||||
for attempt < maxAttempts {
|
||||
resp, err = sender.Do(req)
|
||||
// retry on temporary network errors, e.g. transient network failures.
|
||||
if (err != nil && !isTemporaryNetworkError(err)) || resp.StatusCode == http.StatusOK || !containsInt(retries, resp.StatusCode) {
|
||||
return
|
||||
}
|
||||
|
||||
if !delay(resp, req.Context().Done()) {
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
attempt++
|
||||
case <-req.Context().Done():
|
||||
err = req.Context().Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isTemporaryNetworkError(err error) bool {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsInt(ints []int, n int) bool {
|
||||
for _, i := range ints {
|
||||
if i == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func delay(resp *http.Response, cancel <-chan struct{}) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// SetAutoRefresh enables or disables automatic refreshing of stale tokens.
|
||||
func (spt *ServicePrincipalToken) SetAutoRefresh(autoRefresh bool) {
|
||||
spt.autoRefresh = autoRefresh
|
||||
}
|
||||
|
||||
// SetRefreshWithin sets the interval within which if the token will expire, EnsureFresh will
|
||||
// refresh the token.
|
||||
func (spt *ServicePrincipalToken) SetRefreshWithin(d time.Duration) {
|
||||
spt.refreshWithin = d
|
||||
return
|
||||
}
|
||||
|
||||
// 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 Sender) { spt.sender = s }
|
||||
|
||||
// OAuthToken implements the OAuthTokenProvider interface. It returns the current access token.
|
||||
func (spt *ServicePrincipalToken) OAuthToken() string {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.token.OAuthToken()
|
||||
}
|
||||
|
||||
// Token returns a copy of the current token.
|
||||
func (spt *ServicePrincipalToken) Token() Token {
|
||||
spt.refreshLock.RLock()
|
||||
defer spt.refreshLock.RUnlock()
|
||||
return spt.token
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue