2020-02-21 03:58:17 +00:00
//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"
2024-06-11 20:09:16 +00:00
"fmt"
2020-02-21 03:58:17 +00:00
"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"
2024-06-11 20:09:16 +00:00
"github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal"
2020-02-21 03:58:17 +00:00
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
)
const (
2023-05-11 15:23:47 +02:00
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"
2020-02-21 03:58:17 +00:00
organizationsTenantID = "organizations"
developerSignOnClientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
defaultSuffix = "/.default"
2024-06-11 20:09:16 +00:00
traceNamespace = "Microsoft.Entra"
traceOpGetToken = "GetToken"
traceOpAuthenticate = "Authenticate"
2020-02-21 03:58:17 +00:00
)
2023-05-11 15:23:47 +02:00
var (
// capability CP1 indicates the client application is capable of handling CAE claims challenges
2024-06-11 20:09:16 +00:00
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" )
2023-05-11 15:23:47 +02:00
)
2024-06-11 20:09:16 +00:00
// tokenCachePersistenceOptions contains options for persistent token caching
type tokenCachePersistenceOptions = internal . TokenCachePersistenceOptions
2020-02-21 03:58:17 +00:00
// setAuthorityHost initializes the authority host for credentials. Precedence is:
2024-06-11 20:09:16 +00:00
// 1. cloud.Configuration.ActiveDirectoryAuthorityHost value set by user
// 2. value of AZURE_AUTHORITY_HOST
// 3. default: Azure Public Cloud
2020-02-21 03:58:17 +00:00
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
}
2024-06-11 20:09:16 +00:00
// 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 { "*" }
}
2020-02-21 03:58:17 +00:00
}
2024-06-11 20:09:16 +00:00
cp := make ( [ ] string , len ( tenants ) )
copy ( cp , tenants )
return cp
2020-02-21 03:58:17 +00:00
}
2024-06-11 20:09:16 +00:00
// 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 )
2020-02-21 03:58:17 +00:00
}
2024-06-11 20:09:16 +00:00
func alphanumeric ( r rune ) bool {
return ( '0' <= r && r <= '9' ) || ( 'a' <= r && r <= 'z' ) || ( 'A' <= r && r <= 'Z' )
2020-02-21 03:58:17 +00:00
}
2024-06-11 20:09:16 +00:00
func validTenantID ( tenantID string ) bool {
if len ( tenantID ) < 1 {
return false
}
for _ , r := range tenantID {
if ! ( alphanumeric ( r ) || r == '.' || r == '-' ) {
return false
}
}
return true
2020-02-21 03:58:17 +00:00
}
2024-06-11 20:09:16 +00:00
func doForClient ( client * azcore . Client , r * http . Request ) ( * http . Response , error ) {
2020-02-21 03:58:17 +00:00
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
}
}
2024-06-11 20:09:16 +00:00
// 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 )
2020-02-21 03:58:17 +00:00
if err != nil {
return nil , err
}
return resp , err
}
// enables fakes for test scenarios
2024-06-11 20:09:16 +00:00
type msalConfidentialClient interface {
2020-02-21 03:58:17 +00:00
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 )
2023-05-11 15:23:47 +02:00
AcquireTokenOnBehalfOf ( ctx context . Context , userAssertion string , scopes [ ] string , options ... confidential . AcquireOnBehalfOfOption ) ( confidential . AuthResult , error )
2020-02-21 03:58:17 +00:00
}
// enables fakes for test scenarios
2024-06-11 20:09:16 +00:00
type msalPublicClient interface {
2020-02-21 03:58:17 +00:00
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 )
}