Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding azidentity package #11845

Merged
merged 2 commits into from
Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
432 changes: 432 additions & 0 deletions sdk/azidentity/aad_identity_client.go

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions sdk/azidentity/aad_identity_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azidentity

import (
"net/url"
"testing"
)

func TestAzurePublicCloudParse(t *testing.T) {
_, err := url.Parse(AzurePublicCloud)
if err != nil {
t.Fatalf("Failed to parse default authority host: %v", err)
}
}

func TestAzureChinaParse(t *testing.T) {
_, err := url.Parse(AzureChina)
if err != nil {
t.Fatalf("Failed to parse AzureChina authority host: %v", err)
}
}

func TestAzureGermanyParse(t *testing.T) {
_, err := url.Parse(AzureGermany)
if err != nil {
t.Fatalf("Failed to parse AzureGermany authority host: %v", err)
}
}

func TestAzureGovernmentParse(t *testing.T) {
_, err := url.Parse(AzureGovernment)
if err != nil {
t.Fatalf("Failed to parse AzureGovernment authority host: %v", err)
}
}
210 changes: 210 additions & 0 deletions sdk/azidentity/azidentity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azidentity

import (
"net/http"
"net/url"
"os"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
)

const (
// AzureChina is a global constant to use in order to access the Azure China cloud.
AzureChina = "https://login.chinacloudapi.cn/"
// AzureGermany is a global constant to use in order to access the Azure Germany cloud.
AzureGermany = "https://login.microsoftonline.de/"
// AzureGovernment is a global constant to use in order to access the Azure Government cloud.
AzureGovernment = "https://login.microsoftonline.us/"
// AzurePublicCloud is a global constant to use in order to access the Azure public cloud.
AzurePublicCloud = "https://login.microsoftonline.com/"
// defaultSuffix is a suffix the signals that a string is in scope format
defaultSuffix = "/.default"
)

var (
successStatusCodes = [2]int{
http.StatusOK, // 200
http.StatusCreated, // 201
}
)

type tokenResponse struct {
token *azcore.AccessToken
refreshToken string
}

// AADAuthenticationFailedError is used to unmarshal error responses received from Azure Active Directory.
type AADAuthenticationFailedError struct {
Message string `json:"error"`
Description string `json:"error_description"`
Timestamp string `json:"timestamp"`
TraceID string `json:"trace_id"`
CorrelationID string `json:"correlation_id"`
URI string `json:"error_uri"`
Response *azcore.Response
}

func (e *AADAuthenticationFailedError) Error() string {
msg := e.Message
if len(e.Description) > 0 {
msg += " " + e.Description
}
return msg
}

// AuthenticationFailedError is returned when the authentication request has failed.
type AuthenticationFailedError struct {
inner error
msg string
}

// Unwrap method on AuthenticationFailedError provides access to the inner error if available.
func (e *AuthenticationFailedError) Unwrap() error {
return e.inner
}

// IsNotRetriable returns true indicating that this is a terminal error.
func (e *AuthenticationFailedError) IsNotRetriable() bool {
return true
}

func (e *AuthenticationFailedError) Error() string {
if len(e.msg) == 0 {
e.msg = e.inner.Error()
}
return e.msg
}

func newAADAuthenticationFailedError(resp *azcore.Response) error {
authFailed := &AADAuthenticationFailedError{Response: resp}
err := resp.UnmarshalAsJSON(authFailed)
if err != nil {
authFailed.Message = resp.Status
authFailed.Description = "Failed to unmarshal response: " + err.Error()
}
return authFailed
}

// CredentialUnavailableError is the error type returned when the conditions required to
// create a credential do not exist or are unavailable.
type CredentialUnavailableError struct {
// CredentialType holds the name of the credential that is unavailable
CredentialType string
// Message contains the reason why the credential is unavailable
Message string
}

func (e *CredentialUnavailableError) Error() string {
return e.CredentialType + ": " + e.Message
}

// IsNotRetriable returns true indicating that this is a terminal error.
func (e *CredentialUnavailableError) IsNotRetriable() bool {
return true
}

// TokenCredentialOptions are used to configure how requests are made to Azure Active Directory.
type TokenCredentialOptions struct {
// The host of the Azure Active Directory authority. The default is https://login.microsoft.com
AuthorityHost *url.URL

// HTTPClient sets the transport for making HTTP requests
// Leave this as nil to use the default HTTP transport
HTTPClient azcore.Transport

// LogOptions configures the built-in request logging policy behavior
LogOptions azcore.RequestLogOptions

// Retry configures the built-in retry policy behavior
Retry *azcore.RetryOptions

// Telemetry configures the built-in telemetry policy behavior
Telemetry azcore.TelemetryOptions
}

// setDefaultValues initializes an instance of TokenCredentialOptions with default settings.
func (c *TokenCredentialOptions) setDefaultValues() (*TokenCredentialOptions, error) {
authorityHost := AzurePublicCloud
if envAuthorityHost := os.Getenv("AZURE_AUTHORITY_HOST"); envAuthorityHost != "" {
authorityHost = envAuthorityHost
}

if c == nil {
defaultAuthorityHostURL, err := url.Parse(authorityHost)
if err != nil {
return nil, err
}
c = &TokenCredentialOptions{AuthorityHost: defaultAuthorityHostURL}
}

if c.AuthorityHost == nil {
defaultAuthorityHostURL, err := url.Parse(authorityHost)
if err != nil {
return nil, err
}
c.AuthorityHost = defaultAuthorityHostURL
}

if len(c.AuthorityHost.Path) == 0 || c.AuthorityHost.Path[len(c.AuthorityHost.Path)-1:] != "/" {
c.AuthorityHost.Path = c.AuthorityHost.Path + "/"
}

return c, nil
}

// newDefaultPipeline creates a pipeline using the specified pipeline options.
func newDefaultPipeline(o TokenCredentialOptions) azcore.Pipeline {
if o.HTTPClient == nil {
o.HTTPClient = azcore.DefaultHTTPClientTransport()
}

return azcore.NewPipeline(
o.HTTPClient,
azcore.NewTelemetryPolicy(o.Telemetry),
azcore.NewUniqueRequestIDPolicy(),
azcore.NewRetryPolicy(o.Retry),
azcore.NewRequestLogPolicy(o.LogOptions))
}

// newDefaultMSIPipeline creates a pipeline using the specified pipeline options needed
// for a Managed Identity, such as a MSI specific retry policy.
func newDefaultMSIPipeline(o ManagedIdentityCredentialOptions) azcore.Pipeline {
if o.HTTPClient == nil {
o.HTTPClient = azcore.DefaultHTTPClientTransport()
}
var statusCodes []int
// retry policy for MSI is not end-user configurable
retryOpts := azcore.RetryOptions{
MaxRetries: 4,
RetryDelay: 2 * time.Second,
TryTimeout: 1 * time.Minute,
StatusCodes: append(statusCodes,
// The following status codes are a subset of those found in azcore.StatusCodesForRetry, these are the only ones specifically needed for MSI scenarios
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusGatewayTimeout, // 504
http.StatusNotFound,
http.StatusGone,
// all remaining 5xx
http.StatusNotImplemented,
http.StatusHTTPVersionNotSupported,
http.StatusVariantAlsoNegotiates,
http.StatusInsufficientStorage,
http.StatusLoopDetected,
http.StatusNotExtended,
http.StatusNetworkAuthenticationRequired),
}

return azcore.NewPipeline(
o.HTTPClient,
azcore.NewTelemetryPolicy(o.Telemetry),
azcore.NewUniqueRequestIDPolicy(),
azcore.NewRetryPolicy(&retryOpts),
azcore.NewRequestLogPolicy(o.LogOptions))
}
134 changes: 134 additions & 0 deletions sdk/azidentity/azidentity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azidentity

import (
"net/url"
"os"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
)

const (
envHostString = "https://mock.com/"
customHostString = "https://custommock.com/"
)

func Test_AuthorityHost_Parse(t *testing.T) {
_, err := url.Parse(AzurePublicCloud)
if err != nil {
t.Fatalf("Failed to parse default authority host: %v", err)
}
}

func Test_NonNilTokenCredentialOptsNilAuthorityHost(t *testing.T) {
opts := &TokenCredentialOptions{Retry: &azcore.RetryOptions{MaxRetries: 6}}
opts, err := opts.setDefaultValues()
if err != nil {
t.Fatalf("Received an error: %v", err)
}
if opts.AuthorityHost == nil {
t.Fatalf("Did not set default authority host")
}
}

func Test_SetEnvAuthorityHost(t *testing.T) {
err := os.Setenv("AZURE_AUTHORITY_HOST", envHostString)
if err != nil {
t.Fatalf("Unexpected error when initializing environment variables: %v", err)
}

opts := &TokenCredentialOptions{}
opts, err = opts.setDefaultValues()
if opts.AuthorityHost.String() != envHostString {
t.Fatalf("Unexpected error when get host from environment vairable: %v", err)
}

// Unset that host environment vairable to avoid other tests failed.
err = os.Unsetenv("AZURE_AUTHORITY_HOST")
if err != nil {
t.Fatalf("Unexpected error when unset environment vairable: %v", err)
}
}

func Test_CustomAuthorityHost(t *testing.T) {
err := os.Setenv("AZURE_AUTHORITY_HOST", envHostString)
if err != nil {
t.Fatalf("Unexpected error when initializing environment variables: %v", err)
}

customHost, err := url.Parse(customHostString)
if err != nil {
t.Fatalf("Received an error: %v", err)
}

opts := &TokenCredentialOptions{AuthorityHost: customHost}
opts, err = opts.setDefaultValues()
if opts.AuthorityHost.String() != customHostString {
t.Fatalf("Unexpected error when get host from environment vairable: %v", err)
}

// Unset that host environment vairable to avoid other tests failed.
err = os.Unsetenv("AZURE_AUTHORITY_HOST")
if err != nil {
t.Fatalf("Unexpected error when unset environment vairable: %v", err)
}
}

func Test_DefaultAuthorityHost(t *testing.T) {
opts := &TokenCredentialOptions{}
opts, err := opts.setDefaultValues()
if opts.AuthorityHost.String() != AzurePublicCloud {
t.Fatalf("Unexpected error when set default AuthorityHost: %v", err)
}
}

func Test_AzureGermanyAuthorityHost(t *testing.T) {
opts := &TokenCredentialOptions{}
opts, err := opts.setDefaultValues()
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(AzureGermany)
if err != nil {
t.Fatal(err)
}
opts.AuthorityHost = u
if opts.AuthorityHost.String() != AzureGermany {
t.Fatalf("Did not retrieve expected authority host string")
}
}

func Test_AzureChinaAuthorityHost(t *testing.T) {
opts := &TokenCredentialOptions{}
opts, err := opts.setDefaultValues()
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(AzureChina)
if err != nil {
t.Fatal(err)
}
opts.AuthorityHost = u
if opts.AuthorityHost.String() != AzureChina {
t.Fatalf("Did not retrieve expected authority host string")
}
}

func Test_AzureGovernmentAuthorityHost(t *testing.T) {
opts := &TokenCredentialOptions{}
opts, err := opts.setDefaultValues()
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(AzureGovernment)
if err != nil {
t.Fatal(err)
}
opts.AuthorityHost = u
if opts.AuthorityHost.String() != AzureGovernment {
t.Fatalf("Did not retrieve expected authority host string")
}
}
Loading