azure-sdk-for-go/sdk/azidentity/azidentity.go

197 строки
7.2 KiB
Go

//go:build go1.18
// +build go1.18
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azidentity
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)
const (
azureAdditionallyAllowedTenants = "AZURE_ADDITIONALLY_ALLOWED_TENANTS"
azureAuthorityHost = "AZURE_AUTHORITY_HOST"
azureClientCertificatePassword = "AZURE_CLIENT_CERTIFICATE_PASSWORD"
azureClientCertificatePath = "AZURE_CLIENT_CERTIFICATE_PATH"
azureClientID = "AZURE_CLIENT_ID"
azureClientSecret = "AZURE_CLIENT_SECRET"
azureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE"
azurePassword = "AZURE_PASSWORD"
azureRegionalAuthorityName = "AZURE_REGIONAL_AUTHORITY_NAME"
azureTenantID = "AZURE_TENANT_ID"
azureUsername = "AZURE_USERNAME"
organizationsTenantID = "organizations"
developerSignOnClientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
defaultSuffix = "/.default"
traceNamespace = "Microsoft.Entra"
traceOpGetToken = "GetToken"
traceOpAuthenticate = "Authenticate"
)
var (
// capability CP1 indicates the client application is capable of handling CAE claims challenges
cp1 = []string{"CP1"}
errInvalidTenantID = errors.New("invalid tenantID. You can locate your tenantID by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names")
)
// Cache represents a persistent cache that makes authentication data available across processes.
// Construct one with [github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache.New]. This package's
// [persistent user authentication example] shows how to use a persistent cache to reuse user
// logins across application runs. For service principal credential types such as
// [ClientCertificateCredential], simply set the Cache field on the credential options.
//
// [persistent user authentication example]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#example-package-PersistentUserAuthentication
type Cache = internal.Cache
// setAuthorityHost initializes the authority host for credentials. Precedence is:
// 1. cloud.Configuration.ActiveDirectoryAuthorityHost value set by user
// 2. value of AZURE_AUTHORITY_HOST
// 3. default: Azure Public Cloud
func setAuthorityHost(cc cloud.Configuration) (string, error) {
host := cc.ActiveDirectoryAuthorityHost
if host == "" {
if len(cc.Services) > 0 {
return "", errors.New("missing ActiveDirectoryAuthorityHost for specified cloud")
}
host = cloud.AzurePublic.ActiveDirectoryAuthorityHost
if envAuthorityHost := os.Getenv(azureAuthorityHost); envAuthorityHost != "" {
host = envAuthorityHost
}
}
u, err := url.Parse(host)
if err != nil {
return "", err
}
if u.Scheme != "https" {
return "", errors.New("cannot use an authority host without https")
}
return host, nil
}
// resolveAdditionalTenants returns a copy of tenants, simplified when tenants contains a wildcard
func resolveAdditionalTenants(tenants []string) []string {
if len(tenants) == 0 {
return nil
}
for _, t := range tenants {
// a wildcard makes all other values redundant
if t == "*" {
return []string{"*"}
}
}
cp := make([]string, len(tenants))
copy(cp, tenants)
return cp
}
// resolveTenant returns the correct tenant for a token request
func resolveTenant(defaultTenant, specified, credName string, additionalTenants []string) (string, error) {
if specified == "" || specified == defaultTenant {
return defaultTenant, nil
}
if defaultTenant == "adfs" {
return "", errors.New("ADFS doesn't support tenants")
}
if !validTenantID(specified) {
return "", errInvalidTenantID
}
for _, t := range additionalTenants {
if t == "*" || t == specified {
return specified, nil
}
}
return "", fmt.Errorf(`%s isn't configured to acquire tokens for tenant %q. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add "*" to allow acquiring tokens for any tenant`, credName, specified)
}
func alphanumeric(r rune) bool {
return ('0' <= r && r <= '9') || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z')
}
func validTenantID(tenantID string) bool {
if len(tenantID) < 1 {
return false
}
for _, r := range tenantID {
if !(alphanumeric(r) || r == '.' || r == '-') {
return false
}
}
return true
}
func doForClient(client *azcore.Client, r *http.Request) (*http.Response, error) {
req, err := runtime.NewRequest(r.Context(), r.Method, r.URL.String())
if err != nil {
return nil, err
}
if r.Body != nil && r.Body != http.NoBody {
// create a rewindable body from the existing body as required
var body io.ReadSeekCloser
if rsc, ok := r.Body.(io.ReadSeekCloser); ok {
body = rsc
} else {
b, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
body = streaming.NopCloser(bytes.NewReader(b))
}
err = req.SetBody(body, r.Header.Get("Content-Type"))
if err != nil {
return nil, err
}
}
// copy headers to the new request, ignoring any for which the new request has a value
h := req.Raw().Header
for key, vals := range r.Header {
if _, has := h[key]; !has {
for _, val := range vals {
h.Add(key, val)
}
}
}
resp, err := client.Pipeline().Do(req)
if err != nil {
return nil, err
}
return resp, err
}
// enables fakes for test scenarios
type msalConfidentialClient interface {
AcquireTokenSilent(ctx context.Context, scopes []string, options ...confidential.AcquireSilentOption) (confidential.AuthResult, error)
AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, options ...confidential.AcquireByAuthCodeOption) (confidential.AuthResult, error)
AcquireTokenByCredential(ctx context.Context, scopes []string, options ...confidential.AcquireByCredentialOption) (confidential.AuthResult, error)
AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, options ...confidential.AcquireOnBehalfOfOption) (confidential.AuthResult, error)
}
// enables fakes for test scenarios
type msalPublicClient interface {
AcquireTokenSilent(ctx context.Context, scopes []string, options ...public.AcquireSilentOption) (public.AuthResult, error)
AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username string, password string, options ...public.AcquireByUsernamePasswordOption) (public.AuthResult, error)
AcquireTokenByDeviceCode(ctx context.Context, scopes []string, options ...public.AcquireByDeviceCodeOption) (public.DeviceCode, error)
AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, options ...public.AcquireByAuthCodeOption) (public.AuthResult, error)
AcquireTokenInteractive(ctx context.Context, scopes []string, options ...public.AcquireInteractiveOption) (public.AuthResult, error)
}