diff --git a/autorest/adal/config.go b/autorest/adal/config.go index 8c83a917f..b486b648e 100644 --- a/autorest/adal/config.go +++ b/autorest/adal/config.go @@ -15,10 +15,15 @@ package adal // limitations under the License. import ( + "errors" "fmt" "net/url" ) +const ( + activeDirectoryEndpointTemplate = "%s/oauth2/%s%s" +) + // OAuthConfig represents the endpoints needed // in OAuth operations type OAuthConfig struct { @@ -60,7 +65,6 @@ func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiV } api = fmt.Sprintf("?api-version=%s", *apiVersion) } - const activeDirectoryEndpointTemplate = "%s/oauth2/%s%s" u, err := url.Parse(activeDirectoryEndpoint) if err != nil { return nil, err @@ -89,3 +93,59 @@ func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiV DeviceCodeEndpoint: *deviceCodeURL, }, nil } + +// MultiTenantOAuthConfig provides endpoints for primary and aulixiary tenant IDs. +type MultiTenantOAuthConfig interface { + PrimaryTenant() *OAuthConfig + AuxiliaryTenants() []*OAuthConfig +} + +// Options contains optional OAuthConfig creation arguments. +type Options struct { + APIVersion string +} + +func (c Options) apiVersion() string { + if c.APIVersion != "" { + return fmt.Sprintf("?api-version=%s", c.APIVersion) + } + return "1.0" +} + +// NewMultiTenantOAuthConfig creates an object that support multitenant OAuth configuration. +// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/authenticate-multi-tenant for more information. +func NewMultiTenantOAuthConfig(activeDirectoryEndpoint, primaryTenantID string, auxiliaryTenantIDs []string, options Options) (MultiTenantOAuthConfig, error) { + if len(auxiliaryTenantIDs) == 0 || len(auxiliaryTenantIDs) > 3 { + return nil, errors.New("must specify one to three auxiliary tenants") + } + mtCfg := multiTenantOAuthConfig{ + cfgs: make([]*OAuthConfig, len(auxiliaryTenantIDs)+1), + } + apiVer := options.apiVersion() + pri, err := NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, primaryTenantID, &apiVer) + if err != nil { + return nil, fmt.Errorf("failed to create OAuthConfig for primary tenant: %v", err) + } + mtCfg.cfgs[0] = pri + for i := range auxiliaryTenantIDs { + aux, err := NewOAuthConfig(activeDirectoryEndpoint, auxiliaryTenantIDs[i]) + if err != nil { + return nil, fmt.Errorf("failed to create OAuthConfig for tenant '%s': %v", auxiliaryTenantIDs[i], err) + } + mtCfg.cfgs[i+1] = aux + } + return mtCfg, nil +} + +type multiTenantOAuthConfig struct { + // first config in the slice is the primary tenant + cfgs []*OAuthConfig +} + +func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig { + return m.cfgs[0] +} + +func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig { + return m.cfgs[1:] +} diff --git a/autorest/adal/config_test.go b/autorest/adal/config_test.go index aad0b5a11..10584ca9c 100644 --- a/autorest/adal/config_test.go +++ b/autorest/adal/config_test.go @@ -15,81 +15,112 @@ package adal // limitations under the License. import ( + "fmt" "testing" ) -func TestNewOAuthConfig(t *testing.T) { - const testActiveDirectoryEndpoint = "https://login.test.com" - const testTenantID = "tenant-id-test" +const TestAuxTenantPrefix = "aux-tenant-test-" + +var ( + TestAuxTenantIDs = []string{TestAuxTenantPrefix + "0", TestAuxTenantPrefix + "1", TestAuxTenantPrefix + "2"} +) - config, err := NewOAuthConfig(testActiveDirectoryEndpoint, testTenantID) +func TestNewOAuthConfig(t *testing.T) { + config, err := NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } - expected := "https://login.test.com/tenant-id-test/oauth2/authorize?api-version=1.0" + expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=1.0", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/token?api-version=1.0" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token?api-version=1.0", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/devicecode?api-version=1.0" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode?api-version=1.0", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } func TestNewOAuthConfigWithAPIVersionNil(t *testing.T) { - const testActiveDirectoryEndpoint = "https://login.test.com" - const testTenantID = "tenant-id-test" - - config, err := NewOAuthConfigWithAPIVersion(testActiveDirectoryEndpoint, testTenantID, nil) + config, err := NewOAuthConfigWithAPIVersion(TestActiveDirectoryEndpoint, TestTenantID, nil) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } - expected := "https://login.test.com/tenant-id-test/oauth2/authorize" + expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/token" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/devicecode" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } func TestNewOAuthConfigWithAPIVersionNotNil(t *testing.T) { - const testActiveDirectoryEndpoint = "https://login.test.com" - const testTenantID = "tenant-id-test" apiVersion := "2.0" - config, err := NewOAuthConfigWithAPIVersion(testActiveDirectoryEndpoint, testTenantID, &apiVersion) + config, err := NewOAuthConfigWithAPIVersion(TestActiveDirectoryEndpoint, TestTenantID, &apiVersion) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } - expected := "https://login.test.com/tenant-id-test/oauth2/authorize?api-version=2.0" + expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=2.0", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/token?api-version=2.0" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token?api-version=2.0", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } - expected = "https://login.test.com/tenant-id-test/oauth2/devicecode?api-version=2.0" + expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode?api-version=2.0", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } + +func TestNewMultiTenantOAuthConfig(t *testing.T) { + cfg, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, TestAuxTenantIDs, Options{}) + if err != nil { + t.Fatalf("autorest/adal: unexpected error while creating multitenant config: %v", err) + } + expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=1.0", TestTenantID) + if ep := cfg.PrimaryTenant().AuthorizeEndpoint.String(); ep != expected { + t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, ep) + } + aux := cfg.AuxiliaryTenants() + if len(aux) == 0 { + t.Fatal("autorest/adal: unexpected zero-length auxiliary tenants") + } + for i := range aux { + expected := fmt.Sprintf("https://login.test.com/aux-tenant-test-%d/oauth2/authorize?api-version=1.0", i) + if ep := aux[i].AuthorizeEndpoint.String(); ep != expected { + t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, ep) + } + } +} + +func TestNewMultiTenantOAuthConfigFail(t *testing.T) { + _, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, nil, Options{}) + if err == nil { + t.Fatal("autorest/adal: expected non-nil error") + } + _, err = NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, []string{"one", "two", "three", "four"}, Options{}) + if err == nil { + t.Fatal("autorest/adal: expected non-nil error") + } +} diff --git a/autorest/adal/token.go b/autorest/adal/token.go index effa87ab2..4083e76e6 100644 --- a/autorest/adal/token.go +++ b/autorest/adal/token.go @@ -71,6 +71,12 @@ type OAuthTokenProvider interface { OAuthToken() string } +// MultitenantOAuthTokenProvider provides tokens used for multi-tenant authorization. +type MultitenantOAuthTokenProvider interface { + PrimaryOAuthToken() string + AuxiliaryOAuthTokens() []string +} + // TokenRefreshError is an interface used by errors returned during token refresh. type TokenRefreshError interface { error @@ -983,3 +989,67 @@ func (spt *ServicePrincipalToken) Token() Token { defer spt.refreshLock.RUnlock() return spt.inner.Token } + +// MultiTenantServicePrincipalToken contains tokens for multi-tenant authorization. +type MultiTenantServicePrincipalToken struct { + PrimaryToken *ServicePrincipalToken + AuxiliaryTokens []*ServicePrincipalToken +} + +// PrimaryOAuthToken returns the primary authorization token. +func (mt *MultiTenantServicePrincipalToken) PrimaryOAuthToken() string { + return mt.PrimaryToken.OAuthToken() +} + +// AuxiliaryOAuthTokens returns one to three auxiliary authorization tokens. +func (mt *MultiTenantServicePrincipalToken) AuxiliaryOAuthTokens() []string { + tokens := make([]string, len(mt.AuxiliaryTokens)) + for i := range mt.AuxiliaryTokens { + tokens[i] = mt.AuxiliaryTokens[i].OAuthToken() + } + return tokens +} + +// 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 (mt *MultiTenantServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error { + if err := mt.PrimaryToken.EnsureFreshWithContext(ctx); err != nil { + return fmt.Errorf("failed to refresh primary token: %v", err) + } + for _, aux := range mt.AuxiliaryTokens { + if err := aux.EnsureFreshWithContext(ctx); err != nil { + return fmt.Errorf("failed to refresh auxiliary token: %v", err) + } + } + return nil +} + +// NewMultiTenantServicePrincipalToken creates a new MultiTenantServicePrincipalToken with the specified credentials and resource. +func NewMultiTenantServicePrincipalToken(multiTenantCfg MultiTenantOAuthConfig, clientID string, secret string, resource string) (*MultiTenantServicePrincipalToken, error) { + 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 + } + auxTenants := multiTenantCfg.AuxiliaryTenants() + m := MultiTenantServicePrincipalToken{ + AuxiliaryTokens: make([]*ServicePrincipalToken, len(auxTenants)), + } + primary, err := NewServicePrincipalToken(*multiTenantCfg.PrimaryTenant(), clientID, secret, resource) + if err != nil { + return nil, fmt.Errorf("failed to create SPT for primary tenant: %v", err) + } + m.PrimaryToken = primary + for i := range auxTenants { + aux, err := NewServicePrincipalToken(*auxTenants[i], clientID, secret, resource) + if err != nil { + return nil, fmt.Errorf("failed to create SPT for auxiliary tenant: %v", err) + } + m.AuxiliaryTokens[i] = aux + } + return &m, nil +} diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index 9f4f1fa43..601e96430 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -845,6 +845,22 @@ func TestMarshalInnerToken(t *testing.T) { } } +func TestNewMultiTenantServicePrincipalToken(t *testing.T) { + cfg, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, TestAuxTenantIDs, Options{}) + if err != nil { + t.Fatalf("autorest/adal: unexpected error while creating multitenant config: %v", err) + } + mt, err := NewMultiTenantServicePrincipalToken(cfg, "clientID", "superSecret", "resource") + if !strings.Contains(mt.PrimaryToken.inner.OauthConfig.AuthorizeEndpoint.String(), TestTenantID) { + t.Fatal("didn't find primary tenant ID in primary SPT") + } + for i := range mt.AuxiliaryTokens { + if ep := mt.AuxiliaryTokens[i].inner.OauthConfig.AuthorizeEndpoint.String(); !strings.Contains(ep, fmt.Sprintf("%s%d", TestAuxTenantPrefix, i)) { + t.Fatalf("didn't find auxiliary tenant ID in token %s", ep) + } + } +} + func newTokenJSON(expiresOn string, resource string) string { return fmt.Sprintf(`{ "access_token" : "accessToken", diff --git a/autorest/authorization.go b/autorest/authorization.go index 2e24b4b39..380865cd6 100644 --- a/autorest/authorization.go +++ b/autorest/authorization.go @@ -285,3 +285,52 @@ func (ba *BasicAuthorizer) WithAuthorization() PrepareDecorator { return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() } + +// MultiTenantServicePrincipalTokenAuthorizer provides authentication across tenants. +type MultiTenantServicePrincipalTokenAuthorizer interface { + WithAuthorization() PrepareDecorator +} + +// NewMultiTenantServicePrincipalTokenAuthorizer crates a BearerAuthorizer using the given token provider +func NewMultiTenantServicePrincipalTokenAuthorizer(tp adal.MultitenantOAuthTokenProvider) MultiTenantServicePrincipalTokenAuthorizer { + return &multiTenantSPTAuthorizer{tp: tp} +} + +type multiTenantSPTAuthorizer struct { + tp adal.MultitenantOAuthTokenProvider +} + +// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header using the +// primary token along with the auxiliary authorization header using the auxiliary tokens. +// +// By default, the token will be automatically refreshed through the Refresher interface. +func (mt multiTenantSPTAuthorizer) WithAuthorization() PrepareDecorator { + return func(p Preparer) Preparer { + return PreparerFunc(func(r *http.Request) (*http.Request, error) { + r, err := p.Prepare(r) + if err != nil { + return r, err + } + if refresher, ok := mt.tp.(adal.RefresherWithContext); ok { + err = refresher.EnsureFreshWithContext(r.Context()) + if err != nil { + var resp *http.Response + if tokError, ok := err.(adal.TokenRefreshError); ok { + resp = tokError.Response() + } + return r, NewErrorWithError(err, "azure.multiTenantSPTAuthorizer", "WithAuthorization", resp, + "Failed to refresh one or more Tokens for request to %s", r.URL) + } + } + r, err = Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", mt.tp.PrimaryOAuthToken()))) + if err != nil { + return r, err + } + auxTokens := mt.tp.AuxiliaryOAuthTokens() + for i := range auxTokens { + auxTokens[i] = fmt.Sprintf("Bearer %s", auxTokens[i]) + } + return Prepare(r, WithHeader(headerAuxAuthorization, strings.Join(auxTokens, "; "))) + }) + } +} diff --git a/autorest/authorization_test.go b/autorest/authorization_test.go index 85d8b0fe7..1c70409ad 100644 --- a/autorest/authorization_test.go +++ b/autorest/authorization_test.go @@ -250,3 +250,52 @@ func TestBasicAuthorizationPasswordOnly(t *testing.T) { t.Fatalf("BasicAuthorizer#WithAuthorization failed to set %s header", authorization) } } + +type mockMTSPTProvider struct { + p string + a []string +} + +func (m mockMTSPTProvider) PrimaryOAuthToken() string { + return m.p +} + +func (m mockMTSPTProvider) AuxiliaryOAuthTokens() []string { + return m.a +} + +func TestMultitenantAuthorizationOne(t *testing.T) { + mtSPTProvider := mockMTSPTProvider{ + p: "primary", + a: []string{"aux1"}, + } + mt := NewMultiTenantServicePrincipalTokenAuthorizer(mtSPTProvider) + req, err := Prepare(mocks.NewRequest(), mt.WithAuthorization()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if primary := req.Header.Get(headerAuthorization); primary != "Bearer primary" { + t.Fatalf("bad primary authorization header %s", primary) + } + if aux := req.Header.Get(headerAuxAuthorization); aux != "Bearer aux1" { + t.Fatalf("bad auxiliary authorization header %s", aux) + } +} + +func TestMultitenantAuthorizationThree(t *testing.T) { + mtSPTProvider := mockMTSPTProvider{ + p: "primary", + a: []string{"aux1", "aux2", "aux3"}, + } + mt := NewMultiTenantServicePrincipalTokenAuthorizer(mtSPTProvider) + req, err := Prepare(mocks.NewRequest(), mt.WithAuthorization()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if primary := req.Header.Get(headerAuthorization); primary != "Bearer primary" { + t.Fatalf("bad primary authorization header %s", primary) + } + if aux := req.Header.Get(headerAuxAuthorization); aux != "Bearer aux1; Bearer aux2; Bearer aux3" { + t.Fatalf("bad auxiliary authorization header %s", aux) + } +} diff --git a/autorest/azure/auth/auth.go b/autorest/azure/auth/auth.go index 20855d4ab..d852f77a8 100644 --- a/autorest/azure/auth/auth.go +++ b/autorest/azure/auth/auth.go @@ -40,6 +40,7 @@ import ( const ( SubscriptionID = "AZURE_SUBSCRIPTION_ID" TenantID = "AZURE_TENANT_ID" + AuxiliaryTenantIDs = "AZURE_AUXILIARY_TENANT_IDS" ClientID = "AZURE_CLIENT_ID" ClientSecret = "AZURE_CLIENT_SECRET" CertificatePath = "AZURE_CERTIFICATE_PATH" @@ -96,6 +97,7 @@ func GetSettingsFromEnvironment() (s EnvironmentSettings, err error) { } s.setValue(SubscriptionID) s.setValue(TenantID) + s.setValue(AuxiliaryTenantIDs) s.setValue(ClientID) s.setValue(ClientSecret) s.setValue(CertificatePath) @@ -145,6 +147,12 @@ func (settings EnvironmentSettings) GetClientCredentials() (ClientCredentialsCon config := NewClientCredentialsConfig(clientID, secret, tenantID) config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint config.Resource = settings.Values[Resource] + if auxTenants, ok := settings.Values[AuxiliaryTenantIDs]; ok { + config.AuxTenants = strings.Split(auxTenants, ";") + for i := range config.AuxTenants { + config.AuxTenants[i] = strings.TrimSpace(config.AuxTenants[i]) + } + } return config, nil } @@ -546,6 +554,7 @@ type ClientCredentialsConfig struct { ClientID string ClientSecret string TenantID string + AuxTenants []string AADEndpoint string Resource string } @@ -559,13 +568,29 @@ func (ccc ClientCredentialsConfig) ServicePrincipalToken() (*adal.ServicePrincip return adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource) } +// MultiTenantServicePrincipalToken creates a MultiTenantServicePrincipalToken from client credentials. +func (ccc ClientCredentialsConfig) MultiTenantServicePrincipalToken() (*adal.MultiTenantServicePrincipalToken, error) { + oauthConfig, err := adal.NewMultiTenantOAuthConfig(ccc.AADEndpoint, ccc.TenantID, ccc.AuxTenants, adal.Options{}) + if err != nil { + return nil, err + } + return adal.NewMultiTenantServicePrincipalToken(oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource) +} + // Authorizer gets the authorizer from client credentials. func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) { - spToken, err := ccc.ServicePrincipalToken() + if len(ccc.AuxTenants) == 0 { + spToken, err := ccc.ServicePrincipalToken() + if err != nil { + return nil, fmt.Errorf("failed to get SPT from client credentials: %v", err) + } + return autorest.NewBearerAuthorizer(spToken), nil + } + mtSPT, err := ccc.MultiTenantServicePrincipalToken() if err != nil { - return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err) + return nil, fmt.Errorf("failed to get multitenant SPT from client credentials: %v", err) } - return autorest.NewBearerAuthorizer(spToken), nil + return autorest.NewMultiTenantServicePrincipalTokenAuthorizer(mtSPT), nil } // ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate. diff --git a/autorest/azure/auth/auth_test.go b/autorest/azure/auth/auth_test.go index 7c8eb3d29..6c730ff96 100644 --- a/autorest/azure/auth/auth_test.go +++ b/autorest/azure/auth/auth_test.go @@ -20,6 +20,7 @@ import ( "reflect" "testing" + "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" ) @@ -268,3 +269,36 @@ func TestFileClientCertificateAuthorizer(t *testing.T) { t.Fail() } } + +func TestMultitenantClientCredentials(t *testing.T) { + setDefaultEnv() + os.Setenv(AuxiliaryTenantIDs, "aux-tenant-1;aux-tenant-2;aux-tenant3") + defer func() { + os.Setenv(AuxiliaryTenantIDs, "") + }() + settings, err := GetSettingsFromEnvironment() + if err != nil { + t.Fatalf("failed to get settings from environment: %v", err) + } + if settings.Values[AuxiliaryTenantIDs] == "" { + t.Fatal("auxiliary tenant IDs are missing in settings") + } + ccc, err := settings.GetClientCredentials() + if err != nil { + t.Fatalf("failed to get client credentials config: %v", err) + } + if len(ccc.AuxTenants) == 0 { + t.Fatal("auxiliary tenant IDs are missing in config") + } + expected := []string{"aux-tenant-1", "aux-tenant-2", "aux-tenant3"} + if !reflect.DeepEqual(ccc.AuxTenants, expected) { + t.Fatalf("expected auxiliary tenants '%s', got '%s'", expected, ccc.AuxTenants) + } + a, err := ccc.Authorizer() + if err != nil { + t.Fatalf("failed to create authorizer: %v", err) + } + if _, ok := a.(autorest.MultiTenantServicePrincipalTokenAuthorizer); !ok { + t.Fatal("authorizer doesn't implement MultiTenantServicePrincipalTokenAuthorizer") + } +} diff --git a/autorest/preparer.go b/autorest/preparer.go index 6e955a4ba..489140224 100644 --- a/autorest/preparer.go +++ b/autorest/preparer.go @@ -32,9 +32,10 @@ const ( mimeTypeOctetStream = "application/octet-stream" mimeTypeFormPost = "application/x-www-form-urlencoded" - headerAuthorization = "Authorization" - headerContentType = "Content-Type" - headerUserAgent = "User-Agent" + headerAuthorization = "Authorization" + headerAuxAuthorization = "x-ms-authorization-auxiliary" + headerContentType = "Content-Type" + headerUserAgent = "User-Agent" ) // Preparer is the interface that wraps the Prepare method.